如何从异步调用返回响应?

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

我尝试从success回调中返回值,以及将响应分配给函数内部的局部变量并返回该局部变量,但这些方法均未真正返回响应。

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`.

答案

→有关使用不同示例的异步行为的更一般性说明,请参见 为什么在函数内部修改变量后变量没有改变? - 异步代码参考

→如果您已经了解问题,请跳至下面的可能解决方案。

问题

Ajax 中A代表异步 。这意味着发送请求(或接收响应)已从正常执行流程中删除。在您的示例中, $.ajax立即返回,下$.ajax语句return result;会在您调用success回调函数之前执行。

这是一个类比,希望可以使同步流和异步流之间的区别更加清晰:

同步

假设您打了一个电话给朋友,并请他为您找东西。尽管可能要花一些时间,但您还是要等电话并凝视太空,直到您的朋友给您所需的答案。

当您进行包含 “正常” 代码的函数调用时,也会发生相同的情况:

function findItem() {
    var item;
    while(item_not_found) {
        // search
    }
    return item;
}

var item = findItem();

// Do something with item
doSomethingElse();

即使findItem可能要花很长时间才能执行,但var item = findItem();之后的任何代码都将执行var item = findItem();必须等到函数返回结果。

异步

您出于相同的原因再次致电给您的朋友。但是这次您告诉他您很着急,他应该用您的手机给您回电 。您挂断电话,离开房屋,然后按计划做。一旦您的朋友给您回电,您就可以处理他提供给您的信息。

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

findItem(function(item) {
    // Do something with item
});
doSomethingElse();

无需等待响应,而是立即继续执行,并执行 Ajax 调用后的语句。为了最终获得响应,您提供了一个在收到响应后立即调用的函数,即回调函数(注意什么? 回叫 ?)。在调用之后执行的所有语句都将在调用回调之前执行。


解决方案

拥抱 JavaScript 的异步特性!尽管某些异步操作提供了同步对应项(“Ajax” 也是如此),但通常不建议使用它们,尤其是在浏览器上下文中。

你问为什么不好?

JavaScript 在浏览器的 UI 线程中运行,任何长时间运行的进程都将锁定 UI,从而使其无响应。此外,JavaScript 的执行时间有上限,浏览器会询问用户是否继续执行。

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

在下面的内容中,我们将研究三种互为基础的不同解决方案:

  • 带有async/await承诺 (ES2017 +,如果使用转译器或再生器,则在较旧的浏览器中可用)
  • 回调 (在节点中受欢迎)
  • then()承诺 (ES2015 +,如果您使用许多 promise 库之一,则在较旧的浏览器中可用)

在当前浏览器和节点 7 + 中,所有这三个功能均可用。


ES2017 +:带有async/await承诺

2017 年发布的 ECMAScript 版本引入了对异步功能的语法级支持 。借助asyncawait ,您可以以 “同步样式” 编写异步。该代码仍然是异步的,但更易于阅读 / 理解。

async/await建立在 promise 之上: async函数总是返回 promise。 await “解开” 承诺,并导致承诺被解决的值,或者如果承诺被拒绝,则抛出错误。

重要提示:您只能在async函数内使用await 。目前,尚不支持顶层await ,因此您可能必须进行异步 IIFE( 立即调用函数表达式 )才能启动async上下文。

您可以阅读有关async并在 MDN 上await更多信息。

这是一个基于以上延迟的示例:

// 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回调。所以这

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

变成

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

在这里,我们定义了函数 “内联”,但是您可以传递任何函数引用:

function myCallback(result) {
    // Code that depends on 'result'
}

foo(myCallback);

foo本身的定义如下:

function foo(callback) {
    $.ajax({
        // ...
        success: callback
    });
}

callback函数将引用我们在调用foo时传递给我们的函数,然后将其传递给success 。即,一旦 Ajax 请求成功, $.ajax将调用callback并将响应传递给回调(可以用result引用,因为这是我们定义回调的方式)。

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

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)的新功能,但已经具有良好的浏览器支持 。还有许多实现标准 Promises API 的库,并提供其他方法来简化异步函数(例如bluebird )的使用和组合。

承诺是未来价值的容器。当 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);
  });
}

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 调用,我们可以使用如下承诺:

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 摇滚 - JavaScript Promises

旁注:jQuery 的延迟对象

延迟对象是 jQuery 的 promise 的自定义实现(在 Promise API 标准化之前)。它们的行为几乎像 promise,但是暴露了稍微不同的 API。

jQuery 的每个 Ajax 方法都已经返回了 “延迟对象”(实际上是对延迟对象的承诺),您可以从函数中返回它:

function ajax() {
    return $.ajax(...);
}

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

旁注:承诺陷阱

请记住,承诺和递延对象只是将来价值的容器 ,它们不是价值本身。例如,假设您具有以下内容:

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 Deferred 对象,而不是响应从服务器。这意味着if语句将始终获取此 Deferred 对象,将其视为true ,并像用户已登录一样继续进行。

但是解决方法很简单:

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对象, .open false作为第三个参数传递给.open

jQuery 的

如果使用jQuery ,则可以将async选项设置为false 。请注意,自 jQuery 1.8 起不推荐使用此选项。然后,您仍然可以使用success回调或访问jqXHR 对象responseText属性:

function foo() {
    var jqXHR = $.ajax({
        //...
        async: false
    });
    return jqXHR.responseText;
}

如果您使用任何其他 jQuery Ajax 方法,例如$.get$.getJSON等,则必须将其更改为$.ajax (因为您只能将配置参数传递给$.ajax )。

当心!不可能发出同步JSONP请求。 JSONP 本质上始终是异步的(还有一个甚至不考虑此选项的原因)。

如果您在代码中使用 jQuery,则此答案适合您

您的代码应类似于以下内容:

function foo() {
    var httpRequest = new XMLHttpRequest();
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
    return httpRequest.responseText;
}

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

Felix Kling 很好地为使用 jQuery for AJAX 的人编写了答案,我决定为没有使用 jQuery 的人提供替代方案。

请注意,对于那些使用新的fetch API,Angular 或 Promise 的人,我在下面添加了另一个答案


您所面对的

这是另一个答案的 “问题解释” 的简短摘要,如果不确定阅读此内容后,请阅读该内容。

AJAX 中的A代表异步 。这意味着发送请求(或接收响应)已从正常执行流程中删除。在您的示例中, .send立即返回,下.send语句return result;会在您调用success回调函数之前执行。

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

这是一个简单的比喻

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

(小提琴)

的值a返回的undefined ,因为a=5部分还没有被执行。 AJAX 就是这样,您要在服务器有机会告诉浏览器该值之前返回值。

一个可能的解决这个问题是代码重新活跃 ,告诉你的程序在计算完成后做什么。

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 调用,在这种情况下为超时)。

用法是:

getFive(onComplete);

哪个应该在屏幕上提示 “5”。 (提琴)

可能的解决方案

基本上有两种方法可以解决此问题:

  1. 使 AJAX 调用同步(将其称为 SJAX)。
  2. 重组您的代码以使其与回调一起正常工作。

1. 同步 AJAX - 不要这样做!

至于同步 AJAX, 请不要这样做! Felix 的回答提出了一些令人信服的论点,说明为什么这是一个坏主意。总结起来,它将冻结用户的浏览器,直到服务器返回响应并创建非常糟糕的用户体验。这是 MDN 提取的另一个简短原因:

XMLHttpRequest 支持同步和异步通信。但是,一般而言,出于性能方面的考虑,异步请求应比同步请求优先。

简而言之,同步请求会阻止代码的执行…… 这可能会导致严重的问题……

如果必须这样做,可以传递一个标志: 这是如何做的:

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完成时如何反应

所以:

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

成为:

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

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

function myHandler(result) {
    // code that depends on `result`
}
foo(myHandler);

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

现在,让我们定义 foo 本身以采取相应的措施

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 入门指南

XMLHttpRequest 2 (首先阅读Benjamin GruenbaumFelix Kling的答案)

如果您不使用 jQuery,并且想要一个漂亮的简短 XMLHttpRequest 2,它可以在现代浏览器以及移动浏览器上运行,我建议使用这种方式:

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

如你看到的:

  1. 它比列出的所有其他功能短。
  2. 回调是直接设置的(因此没有多余的闭包)。
  3. 它使用新的 onload(因此您不必检查 readystate 和&状态)
  4. 还有一些我不记得的情况使 XMLHttpRequest 1 变得令人讨厌。

有两种方法可以获取此 Ajax 调用的响应(三种使用 XMLHttpRequest var 名称):

最简单的:

this.response

或者,由于某种原因,您bind()回调bind()到一个类:

e.target.response

例:

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

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

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

没有比这容易的了。

现在,有些人可能会说,最好使用 onreadystatechange 甚至 XMLHttpRequest 变量名称。错了

查看XMLHttpRequest 的高级功能

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

仅当您要获取状态 2 的标头时,onreadystatechange 才有用。

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


现在,如果您想使用 post 和 FormData 进行更复杂的操作,则可以轻松扩展此功能:

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)
}

再次... 这是一个非常短的函数,但是它确实可以获取和发布。

用法示例:

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] ):

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

或设置一些自定义值:

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

如您所见,我没有实现同步... 这是一件坏事。

话虽如此... 为什么不做简单的事情呢?


如评论中所述,使用错误 && 同步确实会完全破坏答案。哪种是正确使用 Ajax 的不错的简短方法?

错误处理程序

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 数组缓冲区或其他任何内容,则错误处理程序可能会很有用。

即使您将 “POSTAPAPAP” 作为方法传递,它也不会引发错误。

即使您将'fdggdgilfdghfldj' 作为 formdata 传递,它也不会引发错误。

在第一种情况下,错误是在this.statusText下的displayAjax()内部,因为Method not Allowed

在第二种情况下,它只是有效。您必须在服务器端检查是否传递了正确的发布数据。

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

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

只有this.type设置为 error。

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

因此:如果您能够正确复制和粘贴 URL,则无需进行错误检查。 ;)

PS:作为第一个测试,我编写了 x('x',displayAjax)...,它完全得到了响应... ??? 因此,我检查了 HTML 所在的文件夹,其中有一个名为 “x.xml” 的文件。因此,即使您忘记了文件 XMLHttpRequest 2 的扩展名,也可以找到它 。我哈哈


同步读取文件

不要那样做

如果要阻止浏览器一段时间,请同步加载一个不错的大.txt文件。

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

现在你可以做

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

没有其他方法可以以非异步方式执行此操作。 (是的,使用 setTimeout 循环... 但是认真吗?)

另一点是... 如果您使用的是 API 或仅使用自己列表的文件,或者您总是对每个请求使用不同的功能...

仅当您有一个页面始终加载相同的 XML / JSON 或仅需要一个函数的页面时。在这种情况下,请稍微修改 Ajax 函数并将 b 替换为您的特殊函数。


以上功能仅供基本使用。

如果要扩展功能...

是的你可以。

我使用了许多 API,并且我集成到每个 HTML 页面中的第一个函数是此答案中的第一个 Ajax 函数,仅使用 GET ...

但是,您可以使用 XMLHttpRequest 2 做很多事情:

我做了一个下载管理器(在两边使用简历,文件阅读器,文件系统使用范围),使用画布的各种图像缩放器转换器,使用 base64images 填充 Web SQL 数据库等等。但是在这些情况下,您应该为此创建一个函数目的... 有时您需要一个 blob,数组缓冲区,可以设置标头,覆盖 mimetype 等等,还有更多...

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

如果您使用的是 Promise,则此答案适合您。

这意味着 AngularJS,jQuery(具有延迟),本机 XHR 的替换(获取),EmberJS,BackboneJS 的保存或任何返回 promise 的节点库。

您的代码应类似于以下内容:

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.

Felix Kling 很好地为使用 jQuery 和 AJAX 回调的人们编写了答案。我对本地 XHR 有一个答案。这个答案是针对 promise 在前端或后端的一般用法。


核心问题

浏览器和具有 NodeJS / io.js 的服务器上的 JavaScript 并发模型是异步的响应式的

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

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

这是一个简单的比喻:

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您可以将处理程序附加到 Promise,以提取其值并处理错误。 then处理程序允许链接调用。通过使用返回 API 的 API来创建 Promise。例如,更现代的 AJAX 替换fetch或 jQuery 的$.get返回承诺。

当我们调用.then承诺并从中返回某些内容时 - 我们获得了处理后价值的承诺。如果我们再次兑现诺言,我们将获得惊人的收获,但让我们坚持不懈。

承诺

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

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 转换为使用 Promise 之后,可以使用then进行计数:

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 调用相同,您可以:

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`
})

因此,它也同样有效。我们已经了解到无法从已经异步的调用中返回值,但是我们可以使用 promise 并将它们链接起来以执行处理。现在,我们知道如何从异步调用返回响应。

ES2015(ES6)

ES6 引入了生成器 ,这些生成器可以在中间返回然后再恢复它们所处的位置。这通常对序列很有用,例如:

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。

这个有点棘手但非常强大的技巧使我们可以以同步方式编写异步代码。有几个 “运行器” 可以为您执行此操作,编写一小段代码即可,但超出了此答案的范围。我将在这里使用 Bluebird 的Promise.coroutine ,但还有其他包装器,例如coQ.async

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
});

该方法本身返回一个 promise,我们可以从其他协程中使用它。例如:

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 中,这是进一步标准化的,目前有几个建议,但是您可以await所有建议。通过添加asyncawait关键字,这只是上述 ES6 提案的 “糖”(更精细的语法)。上面的例子:

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 错误。这个想法不是让它返回任何东西,而是将数据传递给称为回调函数的东西,该函数处理数据。

那是:

function handleData( responseData ) {

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

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

在提交处理程序中返回任何内容都不会做任何事情。相反,您必须交出数据,或者直接在成功函数中执行所需的操作。

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

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);    
});

我将以恐怖的手绘漫画来回答。第二张图片是代码示例中undefined result的原因。

在此处输入图片说明

角度 1

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

在这里

承诺可用于嵌套异步功能,并允许将多个功能链接在一起。

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

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

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一起使用。

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

}

你可以这样消耗掉

search() {
    this.searchService.search(this.searchField.value)
      .then((result) => {
    this.result = result.artists.items;
  })
  .catch((error) => console.error(error));
}

在此处查看原始帖子。但是 Typescript 不支持本机 es6 Promises ,如果要使用它,则可能需要插件。

另外,这里是在这里定义的 Promise 规范

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

// WRONG
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.push(result);
    });
});
console.log(results); // E.g., using them, returning them, etc.

例:

// 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));
}
.as-console-wrapper {
  max-height: 100% !important;
}

无效的原因是,在您尝试使用结果时, doSomethingAsync的回调尚未运行。

因此,如果您有一个数组(或某种类型的列表),并且想对每个条目执行异步操作,则有两个选择:并行(重叠)或串行(一个接一个地依次执行)。

平行

您可以启动所有这些,并跟踪期望的回调数量,然后在获得许多回调时使用结果:

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
        }
    });
});

例:

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));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(我们可以消除expecting ,只使用results.length === theArray.length ,但这使我们对在调用未完成时更改theArray的可能性theArray开放theArray 。)

注意,我们如何使用forEachindex将结果保存在与它相关的条目相同位置的results中,即使结果到达的顺序不正确(因为异步调用不一定按开始的顺序完成) )。

但是,如果您需要从函数返回这些结果怎么办?正如其他答案所指出的那样,您不能这样做。您必须让您的函数接受并调用回调(或返回Promise )。这是一个回调版本:

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);
});

例:

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));
}
.as-console-wrapper {
  max-height: 100% !important;
}

或以下是返回Promise的版本:

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来拒绝承诺。)

例:

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));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(或者,您可以为doSomethingAsync做一个包装,该包装返回一个 Promise,然后执行以下操作……)

如果doSomethingAsync给您Promise ,则可以使用Promise.all

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(function(entry) {
        return doSomethingAsync(entry);
    }));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

如果您知道doSomethingAsync将忽略第二个和第三个参数,则可以直接将其传递给mapmap使用三个参数调用其回调,但是大多数人在大多数时间只使用第一个参数):

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

例:

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(doSomethingAsync));
}
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));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

请注意, Promise.all解决所有诺言时提供给您的所有诺言的结果数组来解决其诺言,或者在您给它的第一个诺言被拒绝时拒绝其诺言。

系列

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

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;但是在以下某些示例中,我们没有要使用的索引。)

例:

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));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(或者再次,为doSomethingAsync构建包装器,该包装器会给您一个承诺并执行以下操作...)

如果doSomethingAsync给您一个承诺,如果您可以使用 ES2017 + 语法(也许使用像Babel这样的编译器),则可以将for-ofawait async函数使用:

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);
});

例:

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));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

如果还不能使用 ES2017 + 语法,则可以对“Promise reduce” 模式使用变体(这比通常的 Promise reduce 更为复杂,因为我们没有将结果从一个传递到下一个将结果收集到一个数组中):

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);
});

例:

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));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

... 使用ES2015 + 箭头功能不太麻烦:

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);
});

例:

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));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

看一下这个例子:

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 返回一个已解决的promise (返回res.data.value时已解决)。因此,您可以等待$ http.get请求完成,然后执行console.log(res.joke) (作为常规的异步流程)。

这是 plnkr:

http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/

ES6 方式(异步 - 等待)

(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);
  });
})();