关于javascript:如何从异步调用返回响应?

How do I return the response from an asynchronous call?

我有一个函数foo,它发出一个ajax请求。如何返回foo的响应?

我尝试从success回调返回值,并将响应分配给函数内部的局部变量并返回该变量,但这些方法都没有实际返回响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            result = response;
            // return response; // <- I tried that one as well
        }
    });

    return result;
}

var result = foo(); // It always ends up being `undefined`.


→ For a more general explanation of async behaviour with different examples, please see Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference

Ok.

→ If you already understand the problem, skip to the possible solutions below.

Ok.

问题

ajax中的a代表异步。这意味着发送请求(或者更确切地说,接收响应)会脱离正常的执行流。在您的示例中,$.ajax立即返回,并且在调用您作为success回调传递的函数之前执行下一条语句return result;。好的。

下面是一个类比,希望能使同步流和异步流之间的区别更清楚:好的。同步的

想象一下,你打电话给一个朋友,让他帮你找点东西。虽然这可能需要一段时间,但你还是要等在电话里,凝视着太空,直到你的朋友给你所需要的答案。好的。

当您进行包含"normal"代码的函数调用时,也会发生同样的情况:好的。

1
2
3
4
5
6
7
8
9
10
11
12
function findItem() {
    var item;
    while(item_not_found) {
        // search
    }
    return item;
}

var item = findItem();

// Do something with item
doSomethingElse();

即使执行findItem可能需要很长时间,但任何在var item = findItem();之后出现的代码都必须等到函数返回结果。好的。异步的

你再次打电话给你的朋友也是出于同样的原因。但这次你告诉他你很忙,他应该用你的手机给你回电话。你挂断电话,离开房子,做你计划做的任何事。一旦你的朋友给你回电话,你就要处理他给你的信息。好的。

这正是执行Ajax请求时发生的事情。好的。

1
2
3
4
findItem(function(item) {
    // Do something with item
});
doSomethingElse();

不是等待响应,而是立即继续执行,并在执行Ajax调用之后执行语句。为了最终得到响应,您提供了一个函数,在收到响应后调用该函数,一个回调(注意到什么吗?回电话?)。在调用回调之前执行该调用之后的任何语句。好的。解(S)

拥抱JavaScript的异步特性!虽然某些异步操作提供了同步的对应项(Ajax也是如此),但通常不鼓励使用它们,特别是在浏览器上下文中。好的。

你问为什么不好?好的。

javascript在浏览器的ui线程中运行,任何长时间运行的进程都会锁定该ui,使其无响应。另外,javascript的执行时间有一个上限,浏览器会询问用户是否继续执行。好的。

所有这些都是非常糟糕的用户体验。用户无法判断是否一切正常。此外,对于连接速度较慢的用户来说,效果会更差。好的。

在下面我们将看到三种不同的解决方案,它们都是在彼此之上构建的:好的。

  • async/await的承诺(ES2017+,如果您使用蒸腾器或再生器,在旧浏览器中提供)
  • 回调(在节点中流行)
  • then()的承诺(ES2015+,如果您使用许多承诺库中的一个,则在旧浏览器中提供)

这三种浏览器都可以在当前浏览器和节点7+中使用。好的。ES2017+:与async/await的承诺

2017年发布的ECMAScript版本引入了对异步函数的语法级支持。在asyncawait的帮助下,您可以用"同步样式"编写异步文件。代码仍然是异步的,但更容易阅读/理解。好的。

async/await建立在承诺之上:async函数总是返回一个承诺。await撤销一项承诺,如果该承诺被拒绝,要么导致该承诺被解决的价值,要么抛出一个错误。好的。

重要提示:您只能在async函数内使用await。目前,还不支持顶级的await,因此您可能需要创建一个async iife(立即调用的函数表达式)来启动async上下文。好的。

您可以在MDN上阅读更多关于asyncawait的信息。好的。

下面是一个建立在上述延迟之上的示例:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Using 'superagent' which will return a promise.
var superagent = require('superagent')

// This is isn't declared as `async` because it already returns a promise
function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}


async function getAllBooks() {
  try {
    // GET a list of book IDs of the current user
    var bookIDs = await superagent.get('/user/books');
    // wait for 3 seconds (just for the sake of this example)
    await delay();
    // GET information about each book
    return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
  } catch(error) {
    // If any of the awaited promises was rejected, this catch block
    // would catch the rejection reason
    return null;
  }
}

// Start an IIFE to use `await` at the top level
(async function(){
  let books = await getAllBooks();
  console.log(books);
})();

当前浏览器和节点版本支持async/await。在再生器(或使用再生器的工具,如babel)的帮助下,您还可以通过将代码转换为ES5来支持较旧的环境。好的。让函数接受回调

回调只是传递给另一个函数的函数。另一个函数可以随时调用传递的函数。在异步进程的上下文中,每当异步进程完成时都将调用回调。通常,结果会传递到回调。好的。

在这个问题的例子中,您可以使foo接受回调,并将其用作success回调。所以这个好的。

1
2
var result = foo();
// Code that depends on 'result'

变成好的。

1
2
3
foo(function(result) {
    // Code that depends on 'result'
});

这里我们定义了函数"inline",但是您可以传递任何函数引用:好的。

1
2
3
4
5
function myCallback(result) {
    // Code that depends on 'result'
}

foo(myCallback);

foo本身定义如下:好的。

1
2
3
4
5
6
function foo(callback) {
    $.ajax({
        // ...
        success: callback
    });
}

callback是指我们在调用时传递给foo的函数,我们只需传递给success即可。也就是说,一旦Ajax请求成功,$.ajax将调用callback并将响应传递给回调(可以通过result引用,因为这是我们定义回调的方式)。好的。

您还可以在将响应传递给回调之前对其进行处理:好的。

1
2
3
4
5
6
7
8
9
function foo(callback) {
    $.ajax({
        // ...
        success: function(response) {
            // For example, filter the response
            callback(filtered_response);
        }
    });
}

使用回调编写代码比看起来更容易。毕竟,浏览器中的javascript是严重的事件驱动(dom事件)。接收Ajax响应只是一个事件。当您必须使用第三方代码时,可能会出现一些困难,但大多数问题都可以通过应用程序流进行思考来解决。好的。ES2015+:与then()的承诺

Promise API是EcmaScript 6(ES2015)的一个新功能,但它已经具有良好的浏览器支持。还有许多库实现了标准的PromisesAPI,并提供了额外的方法来简化异步函数(如Bluebird)的使用和组合。好的。

承诺是未来价值的容器。当承诺收到该值(已解决)或取消(拒绝)时,它会通知所有想要访问该值的"侦听器"。好的。

与普通回调相比的优势在于,它们允许您分离代码,并且更容易组合。好的。

以下是使用承诺的简单示例:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}

delay()
  .then(function(v) { // `delay` returns a promise
    console.log(v); // Log the value once it is resolved
  })
  .catch(function(v) {
    // Or do something else if it is rejected
    // (it would not happen in this example, since `reject` is not called).
  });

应用于我们的Ajax调用,我们可以使用如下承诺:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function ajax(url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      resolve(this.responseText);
    };
    xhr.onerror = reject;
    xhr.open('GET', url);
    xhr.send();
  });
}

ajax("/echo/json")
  .then(function(result) {
    // Code depending on result
  })
  .catch(function() {
    // An error occurred
  });

描述Promise提供的所有优势都超出了这个答案的范围,但是如果编写新代码,您应该认真考虑它们。它们提供了代码的一个伟大的抽象和分离。好的。

关于承诺的更多信息:html5 rocks-javascript承诺好的。旁注:jquery的延迟对象

延迟对象是jquery的承诺自定义实现(在Promise API标准化之前)。它们的行为类似于承诺,但公开了略有不同的API。好的。

jquery的每个ajax方法都已经返回了一个"延迟对象"(实际上是一个延迟对象的承诺),您可以从函数返回它:好的。

1
2
3
4
5
6
7
8
9
function ajax() {
    return $.ajax(...);
}

ajax().done(function(result) {
    // Code depending on result
}).fail(function() {
    // An error occurred
});

旁注:承诺

记住承诺和延迟对象只是未来价值的容器,而不是价值本身。例如,假设您有以下条件:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function checkPassword() {
    return $.ajax({
        url: '/password',
        data: {
            username: $('#username').val(),
            password: $('#password').val()
        },
        type: 'POST',
        dataType: 'json'
    });
}

if (checkPassword()) {
    // Tell the user they're logged in
}

此代码误解了上述异步问题。具体来说,$.ajax()在检查服务器上的'/password'页面时不会冻结代码—它向服务器发送请求,在等待时,立即返回jquery-ajax延迟对象,而不是服务器的响应。这意味着if语句将始终获取这个延迟对象,将其视为true,并像用户登录一样继续。不好的。好的。

但解决方法很简单:好的。

1
2
3
4
5
6
7
8
9
10
11
checkPassword()
.done(function(r) {
    if (r) {
        // Tell the user they're logged in
    } else {
        // Tell the user their password was bad
    }
})
.fail(function(x) {
    // Tell the user something bad happened
});

不推荐:同步"Ajax"调用

正如我提到的,一些(!)异步操作具有同步对应项。我不提倡使用它们,但为了完整性起见,下面介绍如何执行同步调用:好的。没有jQuery

如果直接使用XMLHTTPRequest对象,将false作为第三个参数传递给.open。好的。JQuery

如果使用jquery,可以将async选项设置为false。请注意,自jquery 1.8以来,此选项已被弃用。然后,您可以继续使用success回调,也可以访问jqxhr对象的responseText属性:好的。

1
2
3
4
5
6
7
function foo() {
    var jqXHR = $.ajax({
        //...
        async: false
    });
    return jqXHR.responseText;
}

如果使用其他jquery ajax方法,如$.get$.getJSON等,则必须将其更改为$.ajax(因为只能将配置参数传递给$.ajax)。好的。

抬起头来!不可能发出同步JSONP请求。JSONP本质上总是异步的(还有一个原因是甚至不考虑这个选项)。好的。好啊。


如果您的代码中没有使用jquery,那么这个答案是为您准备的。

您的代码应该是这样的:

1
2
3
4
5
6
7
8
function foo() {
    var httpRequest = new XMLHttpRequest();
    httpRequest.open('GET',"/echo/json");
    httpRequest.send();
    return httpRequest.responseText;
}

var result = foo(); // always ends up being 'undefined'

费利克斯·克林为使用jquery for ajax的人写了一个很好的答案,我决定为那些不使用jquery的人提供一个替代方案。

(注意,对于那些使用新的fetchAPI、angular或promises的用户,我在下面添加了另一个答案)

你所面对的

这是另一个答案中"问题解释"的简短摘要,如果阅读后您不确定,请阅读。

ajax中的a代表异步。这意味着发送请求(或者更确切地说,接收响应)会脱离正常的执行流。在您的示例中,.send立即返回,并且在调用您作为success回调传递的函数之前执行下一条语句return result;

这意味着当您返回时,您定义的侦听器尚未执行,这意味着您返回的值尚未定义。

下面是一个简单的类比

1
2
3
4
5
6
7
function getFive(){
    var a;
    setTimeout(function(){
         a=5;
    },10);
    return a;
}

(小提琴)

由于a=5部分尚未执行,返回的a值为undefined。Ajax的行为是这样的,在服务器有机会告诉浏览器该值是什么之前,您将返回该值。

解决这个问题的一个可能的方法是重新编写代码,告诉程序计算完成后该做什么。

1
2
3
4
5
6
7
8
9
10
11
function onComplete(a){ // When the code completes, do this
    alert(a);
}

function getFive(whenDone){
    var a;
    setTimeout(function(){
         a=5;
         whenDone(a);
    },10);
}

这叫做CPS。基本上,我们传递getFive一个动作,当它完成时执行,我们告诉代码在事件完成时如何反应(比如Ajax调用,或者在本例中是超时)。

用途是:

1
getFive(onComplete);

它应该在屏幕上提示"5"。(小提琴)

可能的解决方案

解决这个问题的基本方法有两种:

  • 使Ajax调用同步(我们称之为sjax)。
  • 重新构造代码以正确处理回调。
  • 1。同步Ajax-不要这样做!!

    至于同步Ajax,不要这样做!费利克斯的回答提出了一些令人信服的论据,解释为什么这是一个坏主意。总而言之,它将冻结用户的浏览器,直到服务器返回响应并创建非常糟糕的用户体验。下面是另一个简短的总结,摘自MDN,内容是:

    XMLHttpRequest supports both synchronous and asynchronous communications. In general, however, asynchronous requests should be preferred to synchronous requests for performance reasons.

    In short, synchronous requests block the execution of code... ...this can cause serious issues...

    如果必须这样做,您可以传递一个标志:以下是方法:

    1
    2
    3
    4
    5
    6
    7
    var request = new XMLHttpRequest();
    request.open('GET', 'yourURL', false);  // `false` makes the request synchronous
    request.send(null);

    if (request.status === 200) {// That's HTTP for 'ok'
      console.log(request.responseText);
    }

    2。重组代码

    让函数接受回调。在示例代码中,可以使foo接受回调。当foo完成时,我们将告诉代码如何反应。

    所以:

    1
    2
    var result = foo();
    // code that depends on `result` goes here

    变成:

    1
    2
    3
    foo(function(result) {
        // code that depends on `result`
    });

    在这里,我们传递了一个匿名函数,但是我们可以很容易地传递对现有函数的引用,使其看起来像:

    1
    2
    3
    4
    function myHandler(result) {
        // code that depends on `result`
    }
    foo(myHandler);

    有关如何完成这种回调设计的更多详细信息,请查看Felix的答案。

    现在,让我们定义foo本身以便采取相应的行动

    1
    2
    3
    4
    5
    6
    7
    8
    function foo(callback) {
        var httpRequest = new XMLHttpRequest();
        httpRequest.onload = function(){ // when the request is loaded
           callback(httpRequest.responseText);// we're calling our method
        };
        httpRequest.open('GET',"/echo/json");
        httpRequest.send();
    }

    (小提琴)

    现在,我们已经让foo函数接受一个在Ajax成功完成时运行的操作,我们可以通过检查响应状态是否为200并相应地进行操作(创建一个失败处理程序等)来进一步扩展这个操作。有效解决我们的问题。

    如果您仍然很难理解这一点,请阅读MDN上的Ajax入门指南。


    XMLHttpRequest2(首先阅读Benjamin Gruenbaum和Felix Kling的答案)好的。

    如果您不使用jquery,并且想要一个适用于现代浏览器和移动浏览器的漂亮的短xmlhttprequest 2,我建议您这样使用:好的。

    1
    2
    3
    4
    5
    6
    function ajax(a, b, c){ // URL, callback, just a placeholder
      c = new XMLHttpRequest;
      c.open('GET', a);
      c.onload = b;
      c.send()
    }

    正如你所看到的:好的。

  • 它比列出的所有其他功能都短。
  • 回调是直接设置的(因此不需要额外的不必要的闭包)。
  • 它使用新的onload(因此您不必检查readystate&;status)
  • 还有一些我不记得的情况会让xmlhttprequest 1烦人。
  • 有两种方法可以获得此Ajax调用的响应(三种方法使用xmlhttpRequest变量名):好的。

    最简单的:好的。

    1
    this.response

    或者,如果出于某种原因,您bind()回调到类:好的。

    1
    e.target.response

    例子:好的。

    1
    2
    3
    4
    function callback(e){
      console.log(this.response);
    }
    ajax('URL', callback);

    或者(上面的一个更好,匿名函数总是一个问题):好的。

    1
    ajax('URL', function(e){console.log(this.response)});

    没有比这更简单的了。好的。

    现在有些人可能会说,最好使用onreadystatechange甚至xmlhttprequest变量名。这是错误的。好的。

    检查xmlhttpRequest高级功能好的。

    它支持所有*现代浏览器。我可以确认,因为存在xmlhttprequest2,所以我正在使用这种方法。在我使用的所有浏览器上,我从来没有遇到过任何类型的问题。好的。

    OnReadyStateChange仅在希望获取状态2上的头时才有用。好的。

    使用XMLHttpRequest变量名是另一个大错误,因为您需要在onload/oreadyStateChange闭包中执行回调,否则会丢失回调。好的。

    现在,如果您希望使用post和formdata实现更复杂的功能,可以轻松地扩展此函数:好的。

    1
    2
    3
    4
    5
    6
    function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val},placeholder
      c = new XMLHttpRequest;
      c.open(e||'get', a);
      c.onload = b;
      c.send(d||null)
    }

    再一次。。。这是一个非常短的函数,但它确实得到&post。好的。

    用法示例:好的。

    1
    2
    x(url, callback); // By default it's get so no need to set
    x(url, callback, 'post', {'key': 'val'}); // No need to set post data

    或者传递一个完整的元素(document.getElementsByTagName('form')[0]:好的。

    1
    2
    var fd = new FormData(form);
    x(url, callback, 'post', fd);

    或者设置一些自定义值:好的。

    1
    2
    3
    var fd = new FormData();
    fd.append('key', 'val')
    x(url, callback, 'post', fd);

    如你所见,我没有实现同步…这是件坏事。好的。

    这么说……为什么不这么简单?好的。

    正如评论中提到的,使用错误和同步确实完全打破了答案的要点。哪种方法是以正确的方式使用Ajax的好方法?好的。

    错误处理程序好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val}, placeholder
      c = new XMLHttpRequest;
      c.open(e||'get', a);
      c.onload = b;
      c.onerror = error;
      c.send(d||null)
    }

    function error(e){
      console.log('--Error--', this.type);
      console.log('this: ', this);
      console.log('Event: ', e)
    }
    function displayAjax(e){
      console.log(e, this);
    }
    x('WRONGURL', displayAjax);

    在上面的脚本中,您有一个静态定义的错误处理程序,因此它不会影响函数。错误处理程序也可以用于其他函数。好的。

    但要真正消除错误,唯一的方法是编写错误的URL,在这种情况下,每个浏览器都会抛出一个错误。好的。

    如果设置自定义头、将responseType设置为blob数组缓冲区或其他任何类型,错误处理程序可能很有用…好的。

    即使将"postapapp"作为方法传递,它也不会抛出错误。好的。

    即使将"fdgdgilfdghfldj"作为formdata传递,也不会引发错误。好的。

    在第一种情况下,错误在this.statusText下的displayAjax()内,与Method not Allowed一样。好的。

    在第二种情况下,它只是工作。如果您通过了正确的日志数据,则必须在服务器端进行检查。好的。

    不允许跨域自动引发错误。好的。

    在错误响应中,没有错误代码。好的。

    只有this.type设置为错误。好的。

    如果完全无法控制错误,为什么还要添加错误处理程序?大多数错误都是在回调函数displayAjax()中返回的。好的。

    所以:如果您能够正确地复制和粘贴URL,就不需要进行错误检查。;)好的。

    PS:作为第一个测试,我写了x("x",displayAjax)…,它完全得到了响应……????所以我检查了HTML所在的文件夹,其中有一个名为"x.xml"的文件。所以即使您忘记了文件的扩展名xmlhttprequest 2也会找到它。我爱你好的。

    同步读取文件好的。

    不要那样做。好的。

    如果您想暂时阻止浏览器,请同步加载一个大的.txt文件。好的。

    1
    2
    3
    4
    5
    6
    function omg(a, c){ // URL
      c = new XMLHttpRequest;
      c.open('GET', a, true);
      c.send();
      return c; // Or c.response
    }

    现在你可以做好的。

    1
     var res = omg('thisIsGonnaBlockThePage.txt');

    没有其他方法可以以非异步方式完成这项工作。(是的,设置超时循环…但是严重吗?)好的。

    另一点是…如果您使用API或您自己列表中的文件,或者您总是为每个请求使用不同的函数…好的。

    只有当你有一个页面,你总是加载相同的XML/JSON或者你只需要一个函数的任何东西时。在这种情况下,稍微修改ajax函数,并用您的特殊函数替换b。好的。

    以上功能仅供基本使用。好的。

    如果要扩展函数…好的。

    是的,你可以。好的。

    我使用了大量的API,我集成到每个HTML页面中的第一个函数就是这个答案中的第一个Ajax函数,其中只有get-only…好的。

    但是你可以用xmlhttprequest 2做很多事情:好的。

    我做了一个下载管理器(在简历、文件阅读器、文件系统的两边都使用范围),各种图像大小调整器转换程序使用画布,用base64图像填充Web SQL数据库等等…但在这些情况下,您应该只为这个目的创建一个函数…有时候你需要一个blob,数组缓冲区,你可以设置头文件,覆盖mimetype,还有更多的…好的。

    但这里的问题是如何返回Ajax响应…(我添加了一个简单的方法。)好的。好啊。


    如果你使用承诺,这个答案是为你。

    这意味着AngularJS、jQuery(带有延迟)、原生XHR替换(fetch)、EmberJS、BackboneJS的save或任何返回承诺的节点库。好的。

    您的代码应该是这样的:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function foo() {
        var data;
        // or $.get(...).then, or request(...).then, or query(...).then
        fetch("/echo/json").then(function(response){
            data = response.json();
        });
        return data;
    }

    var result = foo(); // result is always undefined no matter what.

    FelixKling为使用jQuery和Ajax回调的用户编写了一个很好的答案。我有一个本地XHR的答案。这个答案适用于前端或后端承诺的一般用法。好的。核心问题

    浏览器和服务器上使用nodejs/io.js的javascript并发模型是异步的和反应式的。好的。

    每当调用返回Promise的方法时,then处理程序总是异步执行,也就是说,在它们下面不在.then处理程序中的代码之后。好的。

    这意味着当您返回data时,您定义的then处理程序尚未执行。这意味着您返回的值没有及时设置为正确的值。好的。

    以下是对这个问题的简单类比:

    1
    2
    3
    4
    5
    6
    7
    8
        function getFive(){
            var data;
            setTimeout(function(){ // set a timer for one second in the future
               data = 5; // after a second, do this
            }, 1000);
            return data;
        }
        document.body.innerHTML = getFive(); // `undefined` here and not 5

    好的。

    由于data = 5部分尚未执行,因此data的价值为undefined。它可能会在一秒钟内执行,但到那时它与返回值无关。好的。

    由于操作尚未发生(Ajax、服务器调用、IO、计时器),所以在请求有机会告诉代码该值是什么之前,您将返回该值。好的。

    解决这个问题的一个可能的方法是重新编写代码,告诉程序计算完成后该做什么。承诺在本质上是暂时的(时间敏感的),从而积极地实现这一点。好的。快速回顾承诺

    承诺是一种价值。承诺是有状态的,它们开始时是无价值的,可以解决:好的。

    • 完成意味着计算成功完成。
    • 拒绝意味着计算失败。

    一个承诺只能改变一次状态,此后它将永远保持在同一状态。您可以将then处理程序附加到承诺中,以提取其值并处理错误。then处理程序允许链接调用。承诺是通过使用返回它们的API创建的。例如,更现代的Ajax替代品fetch或jquery的$.get返回承诺。好的。

    当我们对一个承诺打电话给.then并从中返回一些东西时,我们得到一个关于处理价值的承诺。如果我们再答应一次,我们会得到令人惊奇的东西,但我们还是要稳住。好的。带着承诺

    让我们看看如何用承诺来解决上述问题。首先,让我们通过使用Promise构造函数创建延迟函数来演示我们从上面对Promise状态的理解:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    function delay(ms){ // takes amount of milliseconds
        // returns a new promise
        return new Promise(function(resolve, reject){
            setTimeout(function(){ // when the time is up
                resolve(); // change the promise to the fulfilled state
            }, ms);
        });
    }

    现在,在我们将settimeout转换为使用承诺之后,我们可以使用then来计算它:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function delay(ms){ // takes amount of milliseconds
      // returns a new promise
      return new Promise(function(resolve, reject){
        setTimeout(function(){ // when the time is up
          resolve(); // change the promise to the fulfilled state
        }, ms);
      });
    }

    function getFive(){
      // we're RETURNING the promise, remember, a promise is a wrapper over our value
      return delay(100).then(function(){ // when the promise is ready
          return 5; // return the value 5, promises are all about return values
      })
    }
    // we _have_ to wrap it like this in the call site, we can't access the plain value
    getFive().then(function(five){
       document.body.innerHTML = five;
    });

    好的。

    基本上,我们不是返回一个由于并发模型而无法返回的值,而是返回一个包装器,用于一个可以用then解包的值。它就像一个可以用then打开的盒子。好的。应用此

    这与最初的API调用相同,您可以:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function foo() {
        // RETURN the promise
        return fetch("/echo/json").then(function(response){
            return response.json(); // process it inside the `then`
        });
    }

    foo().then(function(response){
        // access the value inside the `then`
    })

    所以这也同样有效。我们已经了解到,我们不能从已经异步的调用中返回值,但是我们可以使用承诺并将它们链接起来以执行处理。现在我们知道了如何从异步调用返回响应。好的。ES2015(ES6)

    ES6引入了生成器,这些函数可以在中间返回,然后恢复到原来的位置。这通常对序列有用,例如:好的。

    1
    2
    3
    4
    5
    function* foo(){ // notice the star, this is ES6 so new browsers/node/io only
        yield 1;
        yield 2;
        while(true) yield 3;
    }

    是一个函数,它返回可以迭代的序列1,2,3,3,3,3,....上的迭代器。虽然这本身就很有趣,并且为很多可能性打开了空间,但有一个特别有趣的案例。好的。

    如果我们生成的序列是一系列动作而不是数字,我们可以在每次生成动作时暂停该函数,并在恢复该函数之前等待它。因此,我们需要的不是一系列数字,而是一系列未来的价值——也就是:承诺。好的。

    这个有点棘手但非常强大的技巧让我们以同步的方式编写异步代码。有几个"跑步者"为你做这件事,写一个只是几行代码,但超出了这个答案的范围。我将在这里使用蓝鸟的Promise.coroutine,但还有其他包装,如coQ.async。好的。

    1
    2
    3
    4
    5
    var foo = coroutine(function*(){
        var data = yield fetch("/echo/json"); // notice the yield
        // code here only executes _after_ the request is done
        return data.json(); // data is defined
    });

    这个方法返回一个承诺本身,我们可以从其他协程中使用它。例如:好的。

    1
    2
    3
    4
    5
    6
    7
    var main = coroutine(function*(){
       var bar = yield foo(); // wait our earlier coroutine, it returns a promise
       // server call done here, code below executes when done
       var baz = yield fetch("/api/users/"+bar.userid); // depends on foo's result
       console.log(baz); // runs after both requests done
    });
    main();

    ES2016(ES7)

    在ES7中,这是进一步标准化的,目前有几个建议,但在所有这些建议中,您都可以保证。通过添加asyncawait关键字,这只是ES6提案的"sugar"(更好的语法)。举个例子:好的。

    1
    2
    3
    4
    5
    async function foo(){
        var data = await fetch("/echo/json"); // notice the await
        // code here only executes _after_ the request is done
        return data.json(); // data is defined
    }

    它仍然会做出同样的承诺:)好的。好啊。


    您使用Ajax不正确。其想法是不让它返回任何东西,而是将数据交给一个称为回调函数的处理数据的函数。

    即:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function handleData( responseData ) {

        // Do what you want with the data
        console.log(responseData);
    }

    $.ajax({
        url:"hi.php",
        ...
        success: function ( data, status, XHR ) {
            handleData(data);
        }
    });

    返回提交处理程序中的任何内容都不会做任何事情。相反,您必须放弃数据,或者直接在success函数中使用它来做您想要的事情。


    最简单的解决方案是创建一个javascript函数并为Ajax success回调调用它。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    function callServerAsync(){
        $.ajax({
            url: '...',
            success: function(response) {

                successCallback(response);
            }
        });
    }

    function successCallback(responseObj){
        // Do something like read the response and show data
        alert(JSON.stringify(responseObj)); // Only applicable to JSON response
    }

    function foo(callback) {

        $.ajax({
            url: '...',
            success: function(response) {
               return callback(null, response);
            }
        });
    }

    var result = foo(function(err, result){
              if (!err)
               console.log(result);    
    });


    我想回答一个简单的发现,手工绘制的漫画。第二个原因是为什么result图像undefined在代码的例子。

    enter image description here


    角1

    对于使用AngularJS的人,可以使用Promises来处理这种情况。

    这里说,

    Promises can be used to unnest asynchronous functions and allows one to chain multiple functions together.

    你也可以在这里找到一个很好的解释。

    下面提到的文档中的示例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
      promiseB = promiseA.then(
        function onSuccess(result) {
          return result + 1;
        }
        ,function onError(err) {
          //Handle error
        }
      );

     // promiseB will be resolved immediately after promiseA is resolved
     // and its value will be the result of promiseA incremented by 1.

    Angular2及更高版本

    Angular2中,请看下面的示例,但建议将ObservablesAngular2一起使用。

    1
    2
    3
    4
    5
     search(term: string) {
         return this.http
      .get(`https://api.spotify.com/v1/search?q=${term}&type=artist`)
      .map((response) => response.json())
      .toPromise();

    }

    你可以这样消费,

    1
    2
    3
    4
    5
    6
    7
    search() {
        this.searchService.search(this.searchField.value)
          .then((result) => {
        this.result = result.artists.items;
      })
      .catch((error) => console.error(error));
    }

    请看这里的原始帖子。但typescript不支持本机ES6承诺,如果您想使用它,可能需要插件。

    此外,这里是这里定义的承诺规范。


    这里的大多数答案都为您何时执行单个异步操作提供了有用的建议,但有时,当您需要对数组或其他类似列表的结构中的每个条目执行异步操作时,就会出现这种情况。诱惑是这样做:

    1
    2
    3
    4
    5
    6
    7
    8
    // WRONG
    var results = [];
    theArray.forEach(function(entry) {
        doSomethingAsync(entry, function(result) {
            results.push(result);
        });
    });
    console.log(results); // E.g., using them, returning them, etc.

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // WRONG
    var theArray = [1, 2, 3];
    var results = [];
    theArray.forEach(function(entry) {
        doSomethingAsync(entry, function(result) {
            results.push(result);
        });
    });
    console.log("Results:", results); // E.g., using them, returning them, etc.

    function doSomethingAsync(value, callback) {
        console.log("Starting async operation for" + value);
        setTimeout(function() {
            console.log("Completing async operation for" + value);
            callback(value * 2);
        }, Math.floor(Math.random() * 200));
    }
    1
    2
    3
    .as-console-wrapper {
      max-height: 100% !important;
    }

    不起作用的原因是,当您试图使用结果时,来自doSomethingAsync的回调还没有运行。

    因此,如果您有一个数组(或某种列表),并且想要对每个条目执行异步操作,那么您有两个选项:并行(重叠)或串联(依次)执行操作。

    平行

    您可以启动所有回调并跟踪预期的回调次数,然后在收到如此多的回调后使用结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                console.log("Results:", results); // E.g., using the results
            }
        });
    });

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var theArray = [1, 2, 3];
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                console.log("Results:", results); // E.g., using the results
            }
        });
    });

    function doSomethingAsync(value, callback) {
        console.log("Starting async operation for" + value);
        setTimeout(function() {
            console.log("Completing async operation for" + value);
            callback(value * 2);
        }, Math.floor(Math.random() * 200));
    }
    1
    2
    3
    .as-console-wrapper {
      max-height: 100% !important;
    }

    (我们可以不使用expecting,只使用results.length === theArray.length,但这使我们有可能在通话未完成时更改theArray…)

    请注意,我们如何使用forEach中的index将结果保存到results中与其相关的条目的相同位置,即使结果到达顺序不对(因为异步调用不一定按照启动顺序完成)。

    但是如果您需要从函数返回这些结果呢?正如其他答案所指出的,您不能这样做;您必须让函数接受并调用回调(或者返回一个承诺)。以下是回调版本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function doSomethingWith(theArray, callback) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    callback(results);
                }
            });
        });
    }
    doSomethingWith(theArray, function(results) {
        console.log("Results:", results);
    });

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    function doSomethingWith(theArray, callback) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    callback(results);
                }
            });
        });
    }
    doSomethingWith([1, 2, 3], function(results) {
        console.log("Results:", results);
    });

    function doSomethingAsync(value, callback) {
        console.log("Starting async operation for" + value);
        setTimeout(function() {
            console.log("Completing async operation for" + value);
            callback(value * 2);
        }, Math.floor(Math.random() * 200));
    }
    1
    2
    3
    .as-console-wrapper {
      max-height: 100% !important;
    }

    或者这里有一个返回Promise的版本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function doSomethingWith(theArray) {
        return new Promise(function(resolve) {
            var results = [];
            var expecting = theArray.length;
            theArray.forEach(function(entry, index) {
                doSomethingAsync(entry, function(result) {
                    results[index] = result;
                    if (--expecting === 0) {
                        // Done!
                        resolve(results);
                    }
                });
            });
        });
    }
    doSomethingWith(theArray).then(function(results) {
        console.log("Results:", results);
    });

    当然,如果doSomethingAsync通过了我们的错误,当我们出错时,我们会用reject来拒绝承诺。)

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    function doSomethingWith(theArray) {
        return new Promise(function(resolve) {
            var results = [];
            var expecting = theArray.length;
            theArray.forEach(function(entry, index) {
                doSomethingAsync(entry, function(result) {
                    results[index] = result;
                    if (--expecting === 0) {
                        // Done!
                        resolve(results);
                    }
                });
            });
        });
    }
    doSomethingWith([1, 2, 3]).then(function(results) {
        console.log("Results:", results);
    });

    function doSomethingAsync(value, callback) {
        console.log("Starting async operation for" + value);
        setTimeout(function() {
            console.log("Completing async operation for" + value);
            callback(value * 2);
        }, Math.floor(Math.random() * 200));
    }
    1
    2
    3
    .as-console-wrapper {
      max-height: 100% !important;
    }

    (或者,您可以为返回承诺的doSomethingAsync制作一个包装,然后执行以下操作…)

    如果doSomethingAsync给你承诺,你可以使用Promise.all来:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function doSomethingWith(theArray) {
        return Promise.all(theArray.map(function(entry) {
            return doSomethingAsync(entry, function(result) {
                results.push(result);
            });
        }));
    }
    doSomethingWith(theArray).then(function(results) {
        console.log("Results:", results);
    });

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function doSomethingWith(theArray) {
        return Promise.all(theArray.map(function(entry) {
            return doSomethingAsync(entry, function(result) {
                results.push(result);
            });
        }));
    }
    doSomethingWith([1, 2, 3]).then(function(results) {
        console.log("Results:", results);
    });

    function doSomethingAsync(value) {
        console.log("Starting async operation for" + value);
        return new Promise(function(resolve) {
            setTimeout(function() {
                console.log("Completing async operation for" + value);
                resolve(value * 2);
            }, Math.floor(Math.random() * 200));
        });
    }
    1
    2
    3
    .as-console-wrapper {
      max-height: 100% !important;
    }

    请注意,Promise.all通过一系列的结果来解决它的承诺,这些结果是你在所有的承诺都得到解决时给出的,或者当你给出的第一个承诺被拒绝时拒绝它的承诺。

    系列

    假设您不希望操作并行?如果您想一个接一个地运行它们,您需要等待每个操作完成,然后再开始下一个操作。下面是一个函数的示例,该函数执行此操作并使用结果调用回调:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function doSomethingWith(theArray, callback) {
        var results = [];
        doOne(0);
        function doOne(index) {
            if (index < theArray.length) {
                doSomethingAsync(theArray[index], function(result) {
                    results.push(result);
                    doOne(index + 1);
                });
            } else {
                // Done!
                callback(results);
            }
        }
    }
    doSomethingWith(theArray, function(results) {
        console.log("Results:", results);
    });

    (因为我们是一系列的工作,所以我们可以使用results.push(result),因为我们知道我们不会得到不正常的结果。在上面我们可以使用results[index] = result;,但是在下面的一些示例中,我们没有要使用的索引。)

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    function doSomethingWith(theArray, callback) {
        var results = [];
        doOne(0);
        function doOne(index) {
            if (index < theArray.length) {
                doSomethingAsync(theArray[index], function(result) {
                    results.push(result);
                    doOne(index + 1);
                });
            } else {
                // Done!
                callback(results);
            }
        }
    }
    doSomethingWith([1, 2, 3], function(results) {
        console.log("Results:", results);
    });

    function doSomethingAsync(value, callback) {
        console.log("Starting async operation for" + value);
        setTimeout(function() {
            console.log("Completing async operation for" + value);
            callback(value * 2);
        }, Math.floor(Math.random() * 200));
    }
    1
    2
    3
    .as-console-wrapper {
      max-height: 100% !important;
    }

    (或者,再次为EDOCX1[0]构建一个包装,它给您一个承诺,并执行下面的操作…)

    如果doSomethingAsync向您承诺,如果您可以使用es2017+语法(可能是像babel这样的蒸腾器),您可以使用async函数与for-ofawait一起使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    async function doSomethingWith(theArray) {
        const results = [];
        for (const entry of theArray) {
            results.push(await doSomethingAsync(entry));
        }
        return results;
    }
    doSomethingWith(theArray).then(results => {
        console.log("Results:", results);
    });

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    async function doSomethingWith(theArray) {
        const results = [];
        for (const entry of theArray) {
            results.push(await doSomethingAsync(entry));
        }
        return results;
    }
    doSomethingWith([1, 2, 3]).then(function(results) {
        console.log("Results:", results);
    });

    function doSomethingAsync(value) {
        console.log("Starting async operation for" + value);
        return new Promise(function(resolve) {
            setTimeout(function() {
                console.log("Completing async operation for" + value);
                resolve(value * 2);
            }, Math.floor(Math.random() * 200));
        });
    }
    1
    2
    3
    .as-console-wrapper {
      max-height: 100% !important;
    }

    如果您还不能使用ES2017+语法,您可以使用"Promise Reduce"模式的变体(这比通常的Promise Reduce更复杂,因为我们不会将结果从一个传递到下一个,而是将结果聚集到一个数组中):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function doSomethingWith(theArray) {
        return theArray.reduce(function(p, entry) {
            return p.then(function(results) {
                return doSomethingAsync(entry).then(function(result) {
                    results.push(result);
                    return results;
                });
            });
        }, Promise.resolve([]));
    }
    doSomethingWith(theArray).then(function(results) {
        console.log("Results:", results);
    });

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    function doSomethingWith(theArray) {
        return theArray.reduce(function(p, entry) {
            return p.then(function(results) {
                return doSomethingAsync(entry).then(function(result) {
                    results.push(result);
                    return results;
                });
            });
        }, Promise.resolve([]));
    }
    doSomethingWith([1, 2, 3]).then(function(results) {
        console.log("Results:", results);
    });

    function doSomethingAsync(value) {
        console.log("Starting async operation for" + value);
        return new Promise(function(resolve) {
            setTimeout(function() {
                console.log("Completing async operation for" + value);
                resolve(value * 2);
            }, Math.floor(Math.random() * 200));
        });
    }
    1
    2
    3
    .as-console-wrapper {
      max-height: 100% !important;
    }

    …ES2015+arrow功能不那么麻烦:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function doSomethingWith(theArray) {
        return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
            results.push(result);
            return results;
        })), Promise.resolve([]));
    }
    doSomethingWith(theArray).then(results => {
        console.log("Results:", results);
    });

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function doSomethingWith(theArray) {
        return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
            results.push(result);
            return results;
        })), Promise.resolve([]));
    }
    doSomethingWith([1, 2, 3]).then(function(results) {
        console.log("Results:", results);
    });

    function doSomethingAsync(value) {
        console.log("Starting async operation for" + value);
        return new Promise(function(resolve) {
            setTimeout(function() {
                console.log("Completing async operation for" + value);
                resolve(value * 2);
            }, Math.floor(Math.random() * 200));
        });
    }
    1
    2
    3
    .as-console-wrapper {
      max-height: 100% !important;
    }


    有一个例子:看看这个

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var app = angular.module('plunker', []);

    app.controller('MainCtrl', function($scope,$http) {

        var getJoke = function(){
            return $http.get('http://api.icndb.com/jokes/random').then(function(res){
                return res.data.value;  
            });
        }

        getJoke().then(function(res) {
            console.log(res.joke);
        });
    });

    你可以看到getJoke是返回的时间(当它是决定一res.data.value返回)。所以你等待直到完成,然后http.get美元的请求是执行(console.log(res.joke)是作为一个正常的异步流)。

    这是plnkr:

    embed.plnkr.co http:/ / / / xlnr7hpcaihjxskmjfsg

    es6(=单等待着)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    (function(){
      async function getJoke(){
        let response = await fetch('http://api.icndb.com/jokes/random');
        let data = await response.json();
        return data.value;
      }

      getJoke().then((joke) => {
        console.log(joke);
      });
    })();

    从异步函数返回值的另一种方法是传入一个对象,该对象将存储来自异步函数的结果。

    下面是一个相同的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    var async = require("async");

    // This wires up result back to the caller
    var result = {};
    var asyncTasks = [];
    asyncTasks.push(function(_callback){
        // some asynchronous operation
        $.ajax({
            url: '...',
            success: function(response) {
                result.response = response;
                _callback();
            }
        });
    });

    async.parallel(asyncTasks, function(){
        // result is available after performing asynchronous operation
        console.log(result)
        console.log('Done');
    });

    我正在使用result对象在异步操作期间存储值。这允许结果在异步作业之后仍然可用。

    我经常使用这种方法。我有兴趣知道这种方法在涉及到通过连续模块将结果连接回的地方工作得有多好。


    这是许多新的javascript框架中使用的两种数据绑定方式之一,它们将对您非常有用…

    因此,如果您使用的是角度、反应或任何其他的框架,它们可以进行两种方式的数据绑定,那么这个问题对您来说是简单的,所以简单来说,您的结果在第一阶段是undefined,所以您在收到数据之前就已经得到了result = undefined,那么一旦得到结果,它就会更新并分配给新的值,这个值是Ajax调用的响应…

    但您如何在纯javascript或jquery中实现它,例如您在这个问题中所问的那样?

    您可以使用回调、Promise和Recently Observable来处理它,例如在Promise中,我们有一些函数,如success()或then(),当您的数据准备就绪时将执行这些函数,与Observable上的回调或订阅函数相同。

    例如,在您使用jquery的情况下,您可以这样做:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    $(document).ready(function(){
        function foo() {
            $.ajax({url:"api/data", success: function(data){
                fooDone(data); //after we have data, we pass it to fooDone
            }});
        };

        function fooDone(data) {
            console.log(data); //fooDone has the data and console.log it
        };

        foo(); //call happens here
    });

    要了解更多关于承诺和可观察性的信息,它们是实现异步的新方法。


    虽然承诺和回拨在许多情况下都很有效,但表达以下内容却是一种痛苦:

    1
    2
    3
    4
    if (!name) {
      name = async1();
    }
    async2(name);

    你最终会通过async1;检查name是否未定义,然后相应地调用回调。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    async1(name, callback) {
      if (name)
        callback(name)
      else {
        doSomething(callback)
      }
    }

    async1(name, async2)

    虽然在一些小例子中它是可以的,但是当您有许多类似的情况和涉及错误处理时它会变得烦人。

    Fibers有助于解决问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    var Fiber = require('fibers')

    function async1(container) {
      var current = Fiber.current
      var result
      doSomething(function(name) {
        result = name
        fiber.run()
      })
      Fiber.yield()
      return result
    }

    Fiber(function() {
      var name
      if (!name) {
        name = async1()
      }
      async2(name)
      // Make any number of async calls from here
    }

    你可以在这里签出这个项目。


    简而言之,您必须实现如下回调:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function callback(response) {
        // Here you can do what ever you want with the response object.
        console.log(response);
    }

    $.ajax({
        url:"...",
        success: callback
    });


    我编写的以下示例演示了如何

    • 处理异步HTTP调用;
    • 等待每个API调用的响应;
    • 使用承诺模式;
    • 使用promise.all模式连接多个HTTP调用;

    这个工作示例是独立的。它将定义一个简单的请求对象,该对象使用window XMLHttpRequest对象进行调用。它将定义一个简单的函数,以等待完成一系列承诺。

    语境。该示例查询spotify web api端点,以便搜索给定查询字符串集的playlist对象:

    1
    2
    3
    4
    [
    "search?type=playlist&q=%22doom%20metal%22",
    "search?type=playlist&q=Adele"
    ]

    对于每个项目,新的Promise将触发一个块-ExecutionBlock,分析结果,基于结果数组调度一组新的Promise,即spotify user对象列表,并在ExecutionProfileBlock中异步执行新的HTTP调用。

    然后,您可以看到一个嵌套的Promise结构,它允许您生成多个完全异步的嵌套HTTP调用,并通过Promise.all连接每个调用子集的结果。

    注释最近的spotify searchAPI需要在请求头中指定一个访问令牌:

    1
    -H"Authorization: Bearer {your access token}"

    因此,要运行以下示例,需要将访问令牌放在请求头中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    var spotifyAccessToken ="YourSpotifyAccessToken";
    var console = {
        log: function(s) {
            document.getElementById("console").innerHTML += s +"<br/>"
        }
    }

    // Simple XMLHttpRequest
    // based on https://davidwalsh.name/xmlhttprequest
    SimpleRequest = {
        call: function(what, response) {
            var request;
            if (window.XMLHttpRequest) { // Mozilla, Safari, ...
                request = new XMLHttpRequest();
            } else if (window.ActiveXObject) { // Internet Explorer
                try {
                    request = new ActiveXObject('Msxml2.XMLHTTP');
                }
                catch (e) {
                    try {
                      request = new ActiveXObject('Microsoft.XMLHTTP');
                    } catch (e) {}
                }
            }

            // State changes
            request.onreadystatechange = function() {
                if (request.readyState === 4) { // Done
                    if (request.status === 200) { // Complete
                        response(request.responseText)
                    }
                    else
                        response();
                }
            }
            request.open('GET', what, true);
            request.setRequestHeader("Authorization","Bearer" + spotifyAccessToken);
            request.send(null);
        }
    }

    //PromiseAll
    var promiseAll = function(items, block, done, fail) {
        var self = this;
        var promises = [],
                       index = 0;
        items.forEach(function(item) {
            promises.push(function(item, i) {
                return new Promise(function(resolve, reject) {
                    if (block) {
                        block.apply(this, [item, index, resolve, reject]);
                    }
                });
            }(item, ++index))
        });
        Promise.all(promises).then(function AcceptHandler(results) {
            if (done) done(results);
        }, function ErrorHandler(error) {
            if (fail) fail(error);
        });
    }; //promiseAll

    // LP: deferred execution block
    var ExecutionBlock = function(item, index, resolve, reject) {
        var url ="https://api.spotify.com/v1/"
        url += item;
        console.log( url )
        SimpleRequest.call(url, function(result) {
            if (result) {

                var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) {
                    return item.owner.href;
                })
                resolve(profileUrls);
            }
            else {
                reject(new Error("call error"));
            }
        })
    }

    arr = [
       "search?type=playlist&q=%22doom%20metal%22",
       "search?type=playlist&q=Adele"
    ]

    promiseAll(arr, function(item, index, resolve, reject) {
        console.log("Making request [" + index +"]")
        ExecutionBlock(item, index, resolve, reject);
    }, function(results) { // Aggregated results

        console.log("All profiles received" + results.length);
        //console.log(JSON.stringify(results[0], null, 2));

        ///// promiseall again

        var ExecutionProfileBlock = function(item, index, resolve, reject) {
            SimpleRequest.call(item, function(result) {
                if (result) {
                    var obj = JSON.parse(result);
                    resolve({
                        name: obj.display_name,
                        followers: obj.followers.total,
                        url: obj.href
                    });
                } //result
            })
        } //ExecutionProfileBlock

        promiseAll(results[0], function(item, index, resolve, reject) {
            //console.log("Making request [" + index +"]" + item)
            ExecutionProfileBlock(item, index, resolve, reject);
        }, function(results) { // aggregated results
            console.log("All response received" + results.length);
            console.log(JSON.stringify(results, null, 2));
        }

        , function(error) { // Error
            console.log(error);
        })

        /////

      },
      function(error) { // Error
          console.log(error);
      });
    1
     

    我在这里广泛地讨论了这个解决方案。


    2017年答案:您现在可以在当前的每个浏览器和节点中完全按照自己的意愿进行操作。

    这很简单:

    • 兑现诺言
    • 使用"wait",它将告诉javascript等待将要解析为值的承诺(如HTTP响应)。
    • 向父函数添加'async'关键字

    以下是您的代码的有效版本:

    1
    2
    3
    4
    5
    6
    (async function(){

    var response = await superagent.get('...')
    console.log(response)

    })()

    所有当前浏览器和节点8都支持等待


    您可以使用此自定义库(使用Promise编写)进行远程调用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function $http(apiConfig) {
        return new Promise(function (resolve, reject) {
            var client = new XMLHttpRequest();
            client.open(apiConfig.method, apiConfig.url);
            client.send();
            client.onload = function () {
                if (this.status >= 200 && this.status < 300) {
                    // Performs the function"resolve" when this.status is equal to 2xx.
                    // Your logic here.
                    resolve(this.response);
                }
                else {
                    // Performs the function"reject" when this.status is different than 2xx.
                    reject(this.statusText);
                }
            };
            client.onerror = function () {
                reject(this.statusText);
            };
        });
    }

    简单用法示例:

    1
    2
    3
    4
    5
    6
    7
    8
    $http({
        method: 'get',
        url: 'google.com'
    }).then(function(response) {
        console.log(response);
    }, function(error) {
        console.log(error)
    });

    Js is a single threaded.

    浏览器可分为三部分:

    1)事件循环

    2)Web API

    3)事件队列

    事件循环将永远运行,也就是无限循环。事件队列是将所有函数推送到某个事件上的地方(例如:单击)。这是一个接一个地从队列中执行并放入事件循环中,该事件循环执行此函数,并在执行第一个函数后为下一个函数做好准备。这意味着直到f在事件循环中执行它之前的函数。

    现在让我们假设在一个队列中推送了两个函数,一个用于从服务器获取数据,另一个用于使用该数据。我们先推送队列中的serverRequest()函数,然后使用data()函数。serverRequest函数进入事件循环并调用服务器,因为我们不知道从服务器获取数据需要多长时间。因此,这个过程需要花费时间,因此我们忙于事件循环,从而挂起页面,这就是Web API的作用所在,它从事件循环中获取这个函数,并处理使事件循环免费的服务器,以便我们可以从队列中执行下一个函数。队列中的下一个函数是utilData(),它进入循环,但由于没有数据Av可能会浪费时间,下一个函数的执行会一直持续到队列的末尾。(这称为异步调用,也就是说,在获取数据之前,我们可以做其他事情)

    假设我们的serverRequest()函数在代码中有一个RETURN语句,当我们从服务器Web API返回数据时,它会将数据推送到队列末尾的队列中。当它在队列的末尾被推送时,我们不能利用它的数据,因为队列中没有函数来利用这些数据,因此不可能从异步调用中返回某些内容。

    因此,解决这个问题的方法是回调或承诺。

    这里的一个答案的图片,正确解释了回调的用法…我们将函数(使用从服务器返回的数据的函数)赋给函数调用服务器。

    CallBack

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     function doAjax(callbackFunc, method, url) {
      var xmlHttpReq = new XMLHttpRequest();
      xmlHttpReq.open(method, url);
      xmlHttpReq.onreadystatechange = function() {

          if (xmlHttpReq.readyState == 4 && xmlHttpReq.status == 200) {
            callbackFunc(xmlHttpReq.responseText);
          }


      }
      xmlHttpReq.send(null);

    }

    在我的代码中,它被称为

    1
    2
    3
    4
    5
    6
    7
    8
    function loadMyJson(categoryValue){
      if(categoryValue==="veg")
      doAjax(print,"GET","http://localhost:3004/vegetables");
      else if(categoryValue==="fruits")
      doAjax(print,"GET","http://localhost:3004/fruits");
      else
      console.log("Data not found");
    }

    请阅读此处了解ECMA(2016/17)中用于进行异步调用的新方法(顶部为@felix kling answer)https://stackoverflow.com/a/1422033/7579856


    另一种解决方案是通过顺序执行器nsynjs执行代码。

    如果基础函数是预先确定的

    NSYNJS将依次评估所有承诺,并将承诺结果放入data属性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function synchronousCode() {

        var getURL = function(url) {
            return window.fetch(url).data.text().data;
        };
       
        var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
        console.log('received bytes:',getURL(url).length);
       
    };

    nsynjs.run(synchronousCode,{},function(){
        console.log('synchronousCode done');
    });
    1
    <script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js">

    如果基础函数未被确认

    步骤1。将带有回调的函数包装到NSynJS感知包装中(如果它具有预先确定的版本,则可以跳过此步骤):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var ajaxGet = function (ctx,url) {
        var res = {};
        var ex;
        $.ajax(url)
        .done(function (data) {
            res.data = data;
        })
        .fail(function(e) {
            ex = e;
        })
        .always(function() {
            ctx.resume(ex);
        });
        return res;
    };
    ajaxGet.nsynjsHasCallback = true;

    步骤2。将同步逻辑投入功能:

    1
    2
    3
    function process() {
        console.log('got data:', ajaxGet(nsynjsCtx,"data/file1.json").data);
    }

    步骤3。通过nsynjs同步运行函数:

    1
    2
    3
    nsynjs.run(process,this,function () {
        console.log("synchronous function finished");
    });

    nsynjs将逐步评估所有的操作符和表达式,如果某个慢函数的结果没有准备好,则暂停执行。

    这里有更多的例子:https://github.com/amaksr/nsynjs/tree/master/examples


    这是我们在与JavaScript的"神秘"斗争时面临的一个非常常见的问题。今天让我试着揭开这个谜的神秘面纱。

    让我们从一个简单的javascript函数开始:

    1
    2
    3
    4
    5
    6
    function foo(){
    // do something
     return 'wohoo';
    }

    let bar = foo(); // bar is 'wohoo' here

    这是一个简单的同步函数调用(其中每一行代码都在下一行代码之前"完成了它的工作"),结果与预期相同。

    现在,让我们通过在函数中引入一点延迟来增加一些扭曲,这样所有代码行就不会按顺序"完成"。因此,它将模拟函数的异步行为:

    1
    2
    3
    4
    5
    6
    7
    function foo(){
     setTimeout( ()=>{
       return 'wohoo';
      }, 1000 )
    }

    let bar = foo() // bar is undefined here

    你看,这个延迟刚刚破坏了我们期望的功能!但到底发生了什么?好吧,如果你看代码的话,这是非常合乎逻辑的。函数foo()在执行时不返回任何内容(因此返回值为undefined),但它确实启动了一个计时器,该计时器在1s后执行一个函数以返回"wooo"。但正如您所看到的,分配给bar的值是从foo()立即返回的内容,而不是稍后返回的任何内容。

    那么,我们如何解决这个问题呢?

    让我们向我们的职能部门请求一个承诺。Promise实际上是关于它的含义:它意味着该函数保证您提供将来得到的任何输出。因此,让我们看看上面这个小问题的实际情况:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function foo(){
       return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
        setTimeout ( function(){
          // promise is RESOLVED , when execution reaches this line of code
           resolve('wohoo')// After 1 second, RESOLVE the promise with value 'wohoo'
        }, 1000 )
      })
    }

    let bar ;
    foo().then( res => {
     bar = res;
     console.log(bar) // will print 'wohoo'
    });

    因此,总结是——为了处理异步函数,如基于Ajax的调用等,您可以使用对resolve的承诺值(您打算返回)。因此,简而言之,在异步函数中解析值而不是返回值。

    更新(承诺异步/等待)

    除了使用then/catch来实现承诺之外,还有一种方法。其思想是识别一个异步函数,然后等待这些承诺得到解决,然后再转到下一行代码。它仍然只是引擎盖下的promises,但使用了不同的语法方法。为了使事情更清楚,您可以在下面找到一个比较:

    then/catch版本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function fetchUsers(){
       let users = [];
       getUsers()
       .then(_users => users = _users)
       .catch(err =>{
          throw err
       })
       return users;
     }

    异步/等待版本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
      async function fetchUsers(){
         try{
            let users = await getUsers()
            return users;
         }
         catch(err){
            throw err;
         }
      }


    这里是一些方法与异步请求:

  • "对象浏览器
  • 一个JavaScript库的问答
  • A + promises.js
  • jQuery的推迟
  • XMLHttpRequest API
  • 使用回调函数执行在回答第一个概念
  • jQuery的例子:推迟执行与多个请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    var App = App || {};

    App = {
        getDataFromServer: function(){

          var self = this,
                     deferred = $.Deferred(),
                     requests = [];

          requests.push($.getJSON('request/ajax/url/1'));
          requests.push($.getJSON('request/ajax/url/2'));

          $.when.apply(jQuery, requests).done(function(xhrResponse) {
            return deferred.resolve(xhrResponse.result);
          });
          return deferred;
        },

        init: function(){

            this.getDataFromServer().done(_.bind(function(resp1, resp2) {

               // Do the operations which you wanted to do when you
               // get a response from Ajax, for example, log response.
            }, this));
        }
    };
    App.init();


    ECMAScript 6具有"生成器",允许您以异步方式轻松编程。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function* myGenerator() {
        const callback = yield;
        let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback});
        console.log("response is:", response);

        // examples of other things you can do
        yield setTimeout(callback, 1000);
        console.log("it delayed for 1000ms");
        while (response.statusText ==="error") {
            [response] = yield* anotherGenerator();
        }
    }

    要运行上述代码,请执行以下操作:

    1
    2
    3
    const gen = myGenerator(); // Create generator
    gen.next(); // Start it
    gen.next((...args) => gen.next([...args])); // Set its callback function

    如果需要针对不支持ES6的浏览器,可以通过babel或闭包编译器运行代码来生成ecmascript 5。

    回调...args被包装在一个数组中,在读取它们时会被破坏,这样模式就可以处理具有多个参数的回调。例如,对于节点fs:

    1
    const [err, data] = yield fs.readFile(filePath,"utf-8", callback);


    foo()callback()函数使用A成功。在这样的尝试。它是简单和容易理解。 ;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var lat ="";
    var lon ="";
    function callback(data) {
        lat = data.lat;
        lon = data.lon;
    }
    function getLoc() {
        var url ="http://ip-api.com/json"
        $.getJSON(url, function(data) {
            callback(data);
        });
    }

    getLoc();

    简短回答:您的foo()方法立即返回,而$ajax()调用在函数返回后异步执行。问题是异步调用返回后如何或在何处存储它检索到的结果。

    在这个线程中给出了几个解决方案。也许最简单的方法是将一个对象传递给foo()方法,并在异步调用完成后将结果存储在该对象的一个成员中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function foo(result) {
        $.ajax({
            url: '...',
            success: function(response) {
                result.response = response;   // Store the async result
            }
        });
    }

    var result = { response: null };   // Object to hold the async result
    foo(result);                       // Returns before the async completes

    注意,对foo()的调用仍然不会返回任何有用的信息。但是,异步调用的结果现在将存储在result.response中。


    我们发现自己在一个宇宙中,这个宇宙似乎沿着我们称之为"时间"的维度发展。我们并不真正理解时间是什么,但我们已经开发出抽象和词汇,让我们思考和谈论它:"过去"、"现在"、"未来"、"之前"、"之后"。好的。

    我们建立的计算机系统——越来越多——有时间作为一个重要的维度。某些事情将在未来发生。然后其他事情需要在第一件事情最终发生之后发生。这就是所谓的"异步"的基本概念。在我们日益网络化的世界中,最常见的异步情况是等待一些远程系统响应某些请求。好的。

    考虑一个例子。你给送牛奶的人打电话要些牛奶。当它来的时候,你想把它放在你的咖啡里。你现在不能把牛奶放进咖啡里,因为它还没到。你得等它来了再把它放进咖啡里。换句话说,以下内容不起作用:好的。

    1
    2
    var milk = order_milk();
    put_in_coffee(milk);

    因为JS无法知道它需要等待order_milk完成,然后才能执行put_in_coffee。换句话说,它不知道order_milk是异步的——在未来某个时候才会产生牛奶。JS和其他声明性语言执行一个接一个的语句而不等待。好的。

    对于这个问题,经典的JS方法利用了这样一个事实,即JS支持作为可以传递的第一类对象的函数,将函数作为参数传递给异步请求,然后在将来某个时候完成任务时调用该请求。这就是"回调"方法。看起来是这样的:好的。

    1
    order_milk(put_in_coffee);

    order_milk启动,订购牛奶,然后,当且仅当牛奶到达时,它调用put_in_coffee。好的。

    这种回调方法的问题在于,它污染了用return报告结果的函数的正常语义;相反,函数不能通过调用作为参数提供的回调来报告结果。此外,这种方法在处理较长的事件序列时会很快变得笨拙。例如,假设我想等牛奶被放进咖啡里,然后,只有这样,才能执行第三步,即喝咖啡。我最终需要写这样的东西:好的。

    1
    order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }

    我要把牛奶和牛奶放进去的行动(drink_coffee)都交给put_in_coffee。这样的代码变得难以编写、读取和调试。好的。

    在这种情况下,我们可以将问题中的代码重写为:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    var answer;
    $.ajax('/foo.json') . done(function(response) {
      callback(response.data);
    });

    function callback(data) {
      console.log(data);
    }

    输入承诺

    这就是"承诺"概念的动机,承诺是一种特殊的价值类型,代表某种未来或异步结果。它可以代表已经发生的事情,或者将来将要发生的事情,或者根本不会发生的事情。承诺有一个单一的方法,名为then,当承诺所代表的结果被实现时,您将向它传递一个要执行的操作。好的。

    在牛奶和咖啡的情况下,我们设计order_milk返回牛奶到达的承诺,然后指定put_in_coffee作为then动作,如下所示:好的。

    1
    order_milk() . then(put_in_coffee)

    这样做的一个好处是,我们可以将它们串在一起,以创建未来出现的序列("链接"):好的。

    1
    order_milk() . then(put_in_coffee) . then(drink_coffee)

    让我们将承诺应用于您的特定问题。我们将把请求逻辑包装在一个函数中,该函数返回一个承诺:好的。

    1
    2
    3
    function get_data() {
      return $.ajax('/foo.json');
    }

    实际上,我们所做的就是在对EDOCX1的调用中添加一个return。这是因为jquery的$.ajax已经返回了一种类似承诺的东西。(实际上,在没有详细说明的情况下,我们更愿意结束此呼叫以返回真正的承诺,或者使用$.ajax的替代方法。)现在,如果我们希望加载文件并等待其完成,然后再做一些事情,我们可以简单地说好的。

    1
    get_data() . then(do_something)

    例如,好的。

    1
    2
    get_data() .
      then(function(data) { console.log(data); });

    当使用promises时,我们最终会将许多函数传递到then中,因此使用更紧凑的ES6风格的箭头函数通常会有所帮助:好的。

    1
    2
    get_data() .
      then(data => console.log(data));

    async关键字

    但是对于必须以一种方式编写代码(如果是同步的话)和以一种完全不同的方式编写代码(如果是异步的话),仍然有一些模糊的不满。对于同步,我们写好的。

    1
    2
    a();
    b();

    但是如果a是异步的,我们必须写下承诺好的。

    1
    a() . then(b);

    上面,我们说,"JS没有办法知道它需要等待第一个调用完成,然后再执行第二个"。如果有什么方法可以告诉JS那不是很好吗?原来有--EDOCX1[11]关键字,在一个称为"async"函数的特殊类型的函数中使用。这项功能是即将推出的ES版本的一部分,但在Babel等蒸腾器中已经提供了正确的预设。这让我们可以简单地写好的。

    1
    2
    3
    4
    5
    async function morning_routine() {
      var milk   = await order_milk();
      var coffee = await put_in_coffee(milk);
      await drink(coffee);
    }

    在你的情况下,你可以写一些好的。

    1
    2
    3
    4
    async function foo() {
      data = await get_data();
      console.log(data);
    }

    好啊。


    当然,有很多方法,比如同步请求、承诺,但是根据我的经验,我认为您应该使用回调方法。JavaScript的异步行为是很自然的。因此,您的代码片段可以重写得稍有不同:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function foo() {
        var result;

        $.ajax({
            url: '...',
            success: function(response) {
                myCallback(response);
            }
        });

        return result;
    }

    function myCallback(response) {
        // Does something.
    }


    问题是:

    How do I return the response from an asynchronous call?

    可以解释为:

    How to make asynchronous code look synchronous?

    解决方案是避免回调,并使用承诺和异步/等待的组合。

    我想给出一个Ajax请求的例子。

    (虽然它可以用javascript编写,但我更喜欢用python编写,并使用transcrypt将其编译为javascript。这就足够清楚了。)

    首先启用jquery用法,使$作为S可用:

    1
    __pragma__ ('alias', 'S', '$')

    定义一个返回承诺的函数,在本例中是Ajax调用:

    1
    2
    3
    4
    5
    6
    7
    def read(url: str):
        deferred = S.Deferred()
        S.ajax({'type':"POST", 'url': url, 'data': { },
            'success': lambda d: deferred.resolve(d),
            'error': lambda e: deferred.reject(e)
        })
        return deferred.promise()

    将异步代码用作同步代码:

    1
    2
    3
    4
    5
    6
    async def readALot():
        try:
            result1 = await read("url_1")
            result2 = await read("url_2")
        except Exception:
            console.warn("Reading a lot failed")

    使用ES2017,您应该将其作为功能声明

    1
    2
    3
    4
    async function foo() {
        var response = await $.ajax({url: '...'})
        return response;
    }

    像这样执行。

    1
    2
    3
    4
    5
    6
    (async function() {
        try {
            var result = await foo()
            console.log(result)
        } catch (e) {}
    })()

    或承诺语法

    1
    2
    3
    4
    5
    6
    7
    foo().then(response => {
        console.log(response)

    }).catch(error => {
        console.log(error)

    })


    我们先看看森林再看看树。

    这里有许多信息丰富的答案,其中的细节非常详细,我不会再重复这些答案。在javascript中编程的关键是首先要有正确的总体执行的心理模型。

  • 您的入口点是作为事件的结果执行的。为了例如,带有代码的脚本标记将加载到浏览器中。(因此,您可能需要关注如果需要dom元素,则页面可以运行代码。先施工等)
  • 您的代码执行到完成——不管有多少异步调用它makes——不执行任何回调,包括xhr请求、设置超时、DOM事件处理程序等。等待执行的每个回调都将位于队列中,等待在触发的其他事件全部执行完毕后运行。
  • 每个对XHR请求的回调、设置超时或DOM事件一旦被调用,将运行到完成。
  • 好消息是,如果你很好地理解这一点,你就不必担心比赛条件。首先,您应该了解如何将代码组织为对不同离散事件的响应,以及如何将它们串联到逻辑序列中。您可以使用Promises或更高级别的New Async/Wait作为实现这一目标的工具,或者您也可以滚动自己的工具。

    但在您熟悉实际问题域之前,不应该使用任何战术工具来解决问题。绘制这些依赖关系的映射,以了解什么时候需要运行。尝试一种特别的方法来处理所有这些回调只是不会很好地为您服务。


    与向您抛出代码不同,有两个概念是理解JS如何处理回调和异步性的关键。(这是一个词吗?)

    事件循环和并发模型

    您需要注意三件事:队列、事件循环和堆栈

    从广义上讲,事件循环与项目管理器类似,它不断地监听希望运行的任何函数,并在队列和堆栈之间进行通信。

    1
    2
    3
    while (queue.waitForMessage()) {
       queue.processNextMessage();
    }

    一旦它接收到运行某个东西的消息,就会将它添加到队列中。队列是等待执行的内容的列表(如Ajax请求)。想象一下:

    1
    2
    3
     1. call foo.com/api/bar using foobarFunc
     2. Go perform an infinite loop
     ... and so on

    当其中一条消息要执行时,它会从队列中弹出消息并创建一个堆栈,堆栈就是JS执行消息中的指令所需执行的一切。所以在我们的例子中,它被告知调用foobarFunc

    1
    2
    3
    function foobarFunc (var) {
      console.log(anotherFunction(var));
    }

    因此,foobarfunc需要执行的任何操作(在我们的示例中是anotherFunction)都将被推到堆栈上。执行,然后忘记-事件循环将移动到队列中的下一件事(或侦听消息)

    这里的关键是执行顺序。那就是

    什么时候有东西跑

    当您使用Ajax对外部方进行调用或运行任何异步代码(例如setTimeout)时,javascript在继续之前依赖于响应。

    最大的问题是什么时候能得到答复?答案是我们不知道-所以事件循环在等待消息说"嘿,跑我"。如果JS只是同步地等待这个消息,那么你的应用程序就会冻结,并且会很糟糕。所以JS继续执行队列中的下一个项目,同时等待消息被添加回队列。

    这就是为什么对于异步功能,我们使用称为回调的东西。这有点像字面上的承诺。正如我承诺在某个时候返回某些内容一样,jquery使用特定的回调,称为deffered.donedeffered.faildeffered.always等。你可以在这里看到他们

    因此,您需要做的是传递一个函数,该函数被承诺在某个时刻用传递给它的数据执行。

    因为回调不是立即执行的,而是在以后的某个时间执行的,所以必须将引用传递给未执行的函数。所以

    1
    2
    3
    function foo(bla) {
      console.log(bla)
    }

    所以大多数时候(但不总是)你会通过foo,而不是foo()

    希望这会有点道理。当您遇到这样看起来令人困惑的事情时,我强烈建议您充分阅读文档,至少了解一下它。它将使您成为更好的开发人员。


    使用承诺

    这个问题最完美的答案是使用Promise

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function ajax(method, url, params) {
      return new Promise(function(resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.onload = function() {
          resolve(this.responseText);
        };
        xhr.onerror = reject;
        xhr.open(method, url);
        xhr.send(params);
      });
    }

    用法

    1
    2
    3
    4
    5
    6
    ajax("GET","/test","acrive=1").then(function(result) {
        // Code depending on result
    })
    .catch(function() {
        // An error occurred
    });

    但是等等…

    使用承诺有问题!

    为什么我们要使用我们自己的习俗承诺?

    我使用这个解决方案有一段时间了,直到我发现旧浏览器中有一个错误:

    1
    Uncaught ReferenceError: Promise is not defined

    所以我决定实现我自己的ES3承诺类,如果没有定义的话,它将低于JS编译器。只需在主代码之前添加此代码,然后安全用户承诺!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    if(typeof Promise ==="undefined"){
        function _classCallCheck(instance, Constructor) {
            if (!(instance instanceof Constructor)) {
                throw new TypeError("Cannot call a class as a function");
            }
        }
        var Promise = function () {
            function Promise(main) {
                var _this = this;
                _classCallCheck(this, Promise);
                this.value = undefined;
                this.callbacks = [];
                var resolve = function resolve(resolveValue) {
                    _this.value = resolveValue;
                    _this.triggerCallbacks();
                };
                var reject = function reject(rejectValue) {
                    _this.value = rejectValue;
                    _this.triggerCallbacks();
                };
                main(resolve, reject);
            }
            Promise.prototype.then = function then(cb) {
                var _this2 = this;
                var next = new Promise(function (resolve) {
                    _this2.callbacks.push(function (x) {
                        return resolve(cb(x));
                    });
                });
                return next;
            };
            Promise.prototype.catch = function catch_(cb) {
                var _this2 = this;
                var next = new Promise(function (reject) {
                    _this2.callbacks.push(function (x) {
                        return reject(cb(x));
                    });
                });
                return next;
            };
            Promise.prototype.triggerCallbacks = function triggerCallbacks() {
                var _this3 = this;
                this.callbacks.forEach(function (cb) {
                    cb(_this3.value);
                });
            };
            return Promise;
        }();
    }

    下面是一个有效的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const validateName = async userName => {
      const url ="abc/xyz";
      try {
        const response = await axios.get(url);
        return response.data
      } catch (err) {
        return false;
      }
    };

    validateName("user")
     .then(data => console.log(data))
     .catch(reason => console.log(reason.message))

    使用async/await和Babel这样的蒸腾器,让它在旧浏览器中工作。您还必须从npm:npm i-d babel preset env babel polyfill安装这个babel预设和polyfill。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function getData(ajaxurl) {
      return $.ajax({
        url: ajaxurl,
        type: 'GET',
      });
    };

    async test() {
      try {
        const res = await getData('https://api.icndb.com/jokes/random')
        console.log(res)
      } catch(err) {
        console.log(err);
      }
    }

    test();

    或者,.then回调只是编写相同逻辑的另一种方法。

    1
    2
    3
    getData(ajaxurl).then(function(res) {
        console.log(res)
    }

    将节点上的XHR转换为异步等待的简单代码示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
    var xhttp = new XMLHttpRequest();

    function xhrWrapWithPromise() {
      return new Promise((resolve, reject) => {
        xhttp.onreadystatechange = function() {
          if (this.readyState == 4) {
            if (this.status == 200) {
              resolve(this.responseText);
            } else {
              reject(new Error("Couldn't feth data finally"));
            }
          }
        };
        xhttp.open("GET","https://www.w3schools.com/xml/xmlhttp_info.txt", true);
        xhttp.send();
      });
    }

    //We need to wrap await in Async function so and anonymous IIFE here
    (async _ => {
      try {
        let result = await xhrWrapWithPromise();
        console.log(result);
      } catch (error) {
        console.log(error);
      }
    })();