如何从异步调用返回响应

Posted

技术标签:

【中文标题】如何从异步调用返回响应【英文标题】:How to return the response from an asynchronous call 【发布时间】:2013-01-08 17:06:14 【问题描述】:

我有一个函数foo,它发出一个异步请求。如何从foo 返回响应/结果?

我正在尝试从回调中返回值,并将结果分配给函数内的局部变量并返回该值,但这些方式都没有真正返回响应(它们都返回 undefined 或其他变量result的初始值是)。

接受回调的异步函数示例(使用 jQuery 的 ajax 函数)

function foo() 
    var result;

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

    return result; // It always returns `undefined`

使用 Node.js 的示例:

function foo() 
    var result;

    fs.readFile("path/to/file", function(err, data) 
        result = data;
        // return data; // <- I tried that one as well
    );

    return result; // It always returns `undefined`

使用承诺的then 块的示例:

function foo() 
    var result;

    fetch(url).then(function(response) 
        result = response;
        // return response; // <- I tried that one as well
    );

    return result; // It always returns `undefined`

【问题讨论】:

像这样使用 deasync ***.com/a/47051880/2083877 @SunilKumar 我认为这没什么用。 OP 提出了这个问题和自我回答来记录如何从异步调用中获取响应。建议使用第 3 方模块违背了这样的目的,并且 IMO 该模块引入的范式不是好的做法。 是时候摆脱这个问题中的 jQuery 了吗?这是 2021 年的遗产? @Liam:这只是一个接受回调的异步函数的示例。 有道理,我对标题进行了调整以不强调 jQuery 【参考方案1】:

→ 有关不同示例的异步行为的更一般解释,请参阅 Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference

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

问题

Ajax 中的 A 代表asynchronous。这意味着发送请求(或者更确切地说是接收响应)从正常的执行流程中取出。在您的示例中,$.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(); 之后的任何代码都必须等待,直到函数返回结果。

异步

出于同样的原因,您再次打电话给您的朋友。但是这一次你告诉他你很着急,他应该在你的手机上给你回电话。你挂断电话,离开家,做你打算做的任何事情。一旦你的朋友给你回电话,你就在处理他给你的信息。

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

findItem(function(item) 
    // Do something with the item
);
doSomethingElse();

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


解决方案

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

你问为什么不好?

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

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

在下文中,我们将研究三种不同的解决方案,它们都建立在彼此之上:

Promises with async/await(ES2017+,如果您使用转译器或再生器,则在旧版浏览器中可用) 回调(在节点中流行) Promises with then()(ES2015+,如果您使用众多 Promise 库之一,则可在旧版浏览器中使用)

这三个都在当前浏览器和节点 7+ 中可用。


ES2017+:与async/await 的承诺

2017 年发布的 ECMAScript 版本为异步函数引入了语法级别的支持。在asyncawait 的帮助下,您可以以“同步风格”编写异步。代码仍然是异步的,但更容易阅读/理解。

async/await 建立在 Promise 之上:async 函数总是返回一个 Promise。 await "unwraps" 一个promise,要么导致promise 被解析的值,要么如果promise 被拒绝则抛出一个错误。

重要提示:您只能在 async 函数中使用 await。目前,尚不支持*** await,因此您可能需要创建异步 IIFE (Immediately Invoked Function Expression) 才能启动 async 上下文。

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

下面是一个例子,详细说明上面的延迟函数findItem()

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

当前的browser 和node 版本支持async/await。您还可以借助 regenerator(或使用再生器的工具,例如 Babel)将代码转换为 ES5 来支持旧环境。


让函数接受回调

回调是函数 1 被传递给函数 2 时。函数 2 可以在函数 1 准备就绪时调用它。在异步进程的上下文中,只要异步进程完成,就会调用回调。通常,结果会传递给回调。

在问题的示例中,您可以让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。 IE。一旦 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) 的一个新特性,但它已经有了很好的 browser support。还有许多库实现了标准 Promises API 并提供了其他方法来简化异步函数的使用和组合(例如,bluebird)。

Promise 是 future 值的容器。当 Promise 接收到值(resolved)或被取消(rejected)时,它会通知所有想要访问该值的“侦听器”。

相对于普通回调的优势在于,它们允许您解耦代码并且更容易编写。

这是一个使用承诺的例子:

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

应用于我们的 Ajax 调用,我们可以使用这样的 Promise:

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("https://jsonplaceholder.typicode.com/todos/1")
  .then(function(result) 
    console.log(result); // Code depending on result
  )
  .catch(function() 
    // An error occurred
  );
.as-console-wrapper  max-height: 100% !important; top: 0; 

描述 Promise 提供的所有优势超出了此答案的范围,但如果您编写新代码,则应认真考虑它们。它们为您的代码提供了很好的抽象和分离。

有关承诺的更多信息:html5 rocks - JavaScript Promises。

旁注:jQuery 的延迟对象

Deferred objects 是 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
);

旁注:承诺陷阱

请记住,promise 和 deferred 对象只是未来值的容器,它们不是值本身。例如,假设您有以下内容:

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() 在检查服务器上的“/密码”页面时不会冻结代码 - 它向服务器发送请求,在等待时,它会立即返回一个 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 对象,请将false 作为第三个参数传递给.open

jQuery

如果您使用jQuery,您可以将async 选项设置为false。请注意,自 jQuery 1.8 起,此选项已弃用。 然后,您仍然可以使用success 回调或访问jqXHR object 的responseText 属性:

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

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

请注意!无法发出同步的JSONP 请求。 JSONP 本质上始终是异步的(还有一个理由甚至不考虑这个选项)。

【讨论】:

@Pommy:如果你想使用 jQuery,你必须包含它。请参考docs.jquery.com/Tutorials:Getting_Started_with_jQuery。 在解决方案 1,子 jQuery 中,我无法理解这一行:If you use any other jQuery AJAX method, such as $.get, $.getJSON, etc., you have them to $.ajax.(是的,我意识到在这种情况下我的昵称有点讽刺) @gibberish:嗯,我不知道如何才能更清楚。你看到foo 是如何被调用的并且一个函数被传递给它(foo(function(result) ....);)吗? result在这个函数内部使用,是Ajax请求的响应。要引用这个函数,foo 的第一个参数称为callback 并分配给success,而不是匿名函数。因此,$.ajax 将在请求成功时调用callback。我试着解释一下。 这个问题的聊天已经死了,所以我不确定在哪里提出概述的更改,但我建议:1)将同步部分更改为一个简单的讨论,说明它为什么不好,没有代码示例怎么做。 2) 删除/合并回调示例以仅显示更灵活的延迟方法,我认为对于那些学习 Javascript 的人来说也更容易理解。 @Jessi:我认为你误解了答案的那一部分。如果您希望 Ajax 请求是同步的,则不能使用 $.getJSON。但是,您不应该希望请求是同步的,因此这不适用。您应该使用回调或承诺来处理响应,正如答案前面所述。【参考方案2】:

如果您在代码中使用 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 did a fine job 为使用 jQuery 进行 AJAX 的人写一个答案,但我决定为不使用 jQuery 的人提供一个替代方案。

(Note, for those using the new fetch API, Angular or promises I've added another answer below)


你所面临的

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

AJAX 中的 A 代表 asynchronous。这意味着发送请求(或者更确切地说是接收响应)从正常的执行流程中取出。在您的示例中,.send 立即返回,并且下一条语句 return result; 在您作为 success 回调传递的函数被调用之前执行。

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

这是一个简单的类比:

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

(Fiddle)

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

这个问题的一个可能的解决方案是编写代码re-actively,告诉你的程序在计算完成后要做什么。

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”。 (Fiddle).

可能的解决方案

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

    使 AJAX 调用同步(我们称之为 SJAX)。 重构您的代码以与回调一起正常工作。

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

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

XMLHttpRequest 支持同步和异步通信。但是,一般来说,出于性能原因,应该首选异步请求而不是同步请求。

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

如果你这样做,你可以传递一个标志。 Here is how:

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

(fiddle)

我们现在让我们的 foo 函数接受一个动作,以便在 AJAX 成功完成时运行。我们可以通过检查响应状态是否不是 200 并采取相应措施(创建一个失败处理程序等)来进一步扩展它。它有效地解决了我们的问题。

如果您仍然难以理解这一点,请在 MDN 上 read the AJAX getting started guide。

【讨论】:

“同步请求会阻塞代码的执行,并且会泄漏内存和事件”同步请求怎么会泄漏内存?【参考方案3】:

XMLHttpRequest2(首先,阅读Benjamin Gruenbaum和Felix 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()

如你所见:

    它比列出的所有其他函数都短。 回调是直接设置的(因此没有额外的不必要的关闭)。 它使用新的 onload(因此您不必检查 readystate && 状态) 还有一些其他情况,我不记得了,这让 XMLHttpRequest 1 很烦人。

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

最简单的:

this.response

或者如果你出于某种原因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 advanced features。

它支持所有*现代浏览器。而且我可以确认,自从 XMLHttpRequest 2 创建以来,我一直在使用这种方法。在我使用的任何浏览器中,我从未遇到过任何类型的问题。

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

使用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)

再次...这是一个非常短的函数,但它可以执行GET 和 POST。

使用示例:

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设置为错误

如果您完全无法控制错误,为什么还要添加错误处理程序? 大部分错误都在回调函数displayAjax()的this里面返回。

因此:如果您能够正确复制和粘贴 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 做很多事情:

我制作了一个下载管理器(使用简历、文件阅读器和文件系统两侧的范围)、使用画布的各种图像缩放转换器、使用 base64 图像填充 Web SQL 数据库等等......

但在这些情况下,您应该只为此目的创建一个函数...有时您需要一个 blob、数组缓冲区、您可以设置标题、覆盖 mimetype 等等...

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

【讨论】:

虽然这个答案很好(而且我们都喜欢 XHR2,并且发布文件数据和多部分数据非常棒) - 这显示了使用 JavaScript 发布 XHR 的语法糖 - 你可能想把它放在博客文章中(我喜欢它),甚至放在图书馆里(不确定名称 xajaxxhr 可能更好:))。我看不到它如何解决从 AJAX 调用返回响应的问题。 (有人仍然可以做var res = x("url") 并且不明白为什么它不起作用;))。附带说明 - 如果您从该方法返回 c 会很酷,这样用户就可以使用 error 等。 2.ajax is meant to be async.. so NO var res=x('url').. 这就是这个问题和答案的重点:) @cocco 所以你在 SO answer 中编写了误导性、不可读的代码以节省一些击键?请不要那样做。【参考方案4】:

如果您使用的是 Promise,那么这个答案适合您。

这意味着 AngularJS、jQuery(带延迟)、原生 XHR 的替换(获取)、Ember.js、Backbone.js 的保存或任何返回承诺的 Node.js 库。

你的代码应该是这样的:

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 did a fine job 为使用带有 Ajax 回调的 jQuery 的人写一个答案。我有一个原生 XHR 的答案。此答案适用于前端或后端的 Promise 通用用法。


核心问题

Node.js/io.js 在浏览器和服务器上的 JavaScript 并发模型是 asynchronousreactive

每当您调用返回承诺的方法时,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 的值是undefined,因为data = 5 部分尚未执行。它可能会在一秒钟内执行,但到那时它与返回的值无关。

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

这个问题的一个可能的解决方案是编写代码re-actively,告诉你的程序在计算完成时要做什么。 Promise 通过本质上是临时的(时间敏感的)来积极地实现这一点。

快速回顾承诺

Promise 是一个随时间变化的价值。 Promise 有状态。它们以没有价值的待处理开始,可以解决:

fulfilled 表示计算成功完成。 rejected 表示计算失败。

Promise 只能更改状态一次,之后它将永远保持相同的状态。您可以将then 处理程序附加到promise 以提取它们的值并处理错误。 then 处理程序允许 chaining 的调用。 Promise 由using APIs that return them 创建。例如,更现代的 Ajax 替换 fetch 或 jQuery 的 $.get 返回承诺。

当我们在一个 Promise 上调用 .thenreturn 一些东西时 - 我们得到一个 已处理值 的 Promise。如果我们兑现另一个承诺,我们会得到惊人的东西,但让我们保持冷静。

承诺

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

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

现在,在我们 converted 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, and we can't access the plain value
getFive().then(function(five)
   document.body.innerHTML = five;
);

基本上,不是返回一个 value ,因为并发模型我们不能这样做 - 我们返回一个 wrapper 的值,我们可以 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 引入了generators,这些函数可以在中间返回,然后恢复它们所在的点。这通常对序列很有用,例如:

function* foo() // Notice the star. This is ES6, so new browsers, Nodes.js, and io.js only
    yield 1;
    yield 2;
    while(true) yield 3;

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

如果我们生成的序列是一系列动作而不是数字 - 我们可以在产生动作时暂停函数,并在恢复函数之前等待它。因此,我们需要一个 future 值序列来代替数字序列——即:promises。

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

var foo = coroutine(function*()
    var data = yield fetch("/echo/json"); // Notice the yield
    // The 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
   // The server call is done here, and the 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 are 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

它仍然返回一个相同的承诺:)

【讨论】:

【参考方案5】:

您错误地使用了 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);
    
);

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

【讨论】:

这个答案是完全语义化的......你的成功方法只是回调中的回调。您可以只使用success: handleData,它会起作用。【参考方案6】:

我会用一个看起来很可怕的手绘漫画来回答。第二张图片是您的代码示例中resultundefined 的原因。

【讨论】:

一张图胜过千言万语A 人 - 要求 B 人的详细信息来修理他的车,然后 B 人 - 进行 Ajax 调用并等待服务器对汽车修理细节的响应,当收到响应时,Ajax Success 函数调用 Person B 函数并将响应作为参数传递给它,Person A 接收答案。 如果您在每张图片中添加代码行来说明概念,那就太好了。 同时,开车的人被困在路边。他要求在继续之前修好汽车。他现在一个人在路边等着……他宁愿打电话等状态变化,但机械师不会这样做……机械师说他必须继续工作,不能简单地挂电话。机械师保证会尽快给他回电话。大约 4 小时后,这个人放弃了,打电话给优步。 - 超时示例。 但是使用回调函数,我觉得最后一帧左边的人被强迫给其他人他们的电话号码。相反,他们必须告诉对方,“这就是我想用电话里那个家伙的信息做的所有事情。做所有这些事情,永远不要告诉我。”我错过了什么? @FingLixon 这不是一部完美的漫画:-D。第二张图片应该说明当您尝试过早(在回调发生之前)读取值时会发生什么。第三张图片说明了设置回调方法:左边的人基本上是回调处理程序:一旦信息可用,他将被调用,然后可以随心所欲地使用它。我现在认为在这部漫画中打两个电话是个坏主意:打给商店的电话和打给左边那个人的电话。我应该简化一下,对此感到抱歉。【参考方案7】:

最简单的解决方案是创建一个 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 a 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);
);

【讨论】:

我不知道是谁投了反对票。但这是一项有效的工作,实际上我使用这种方法来创建整个应用程序。 jquery.ajax 不返回数据,因此最好使用上述方法。如果有错误,请解释并提出更好的方法。 对不起,我忘了发表评论(我通常这样做!)。我投了反对票。否决票并不表示事实正确或缺乏,它们表示在上下文中的有用性或缺乏。鉴于 Felix 已经更详细地解释了这一点,我认为您的答案没有用。附带说明一下,如果响应是 JSON,为什么要对响应进行字符串化? ok.. @Benjamin 我使用 stringify 将 JSON 对象转换为字符串。并感谢您澄清您的观点。会记住发布更详细的答案。 如果你想在“successCallback”之外返回“responseObj”怎么办...... :)......你会怎么做......? ...导致简单的返回会将其返回到 ajax 的“成功”回调...而不是在“successCallback”之外...【参考方案8】:

角度 1

使用AngularJS的人可以使用promises来处理这种情况。

Here 它说,

Promise 可用于取消嵌套异步函数,并允许将多个函数链接在一起。

你也可以找到一个很好的解释here。

下面提到的documentation 中的示例。

  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.

Angular 2 及更高版本

在 Angular 2 中查看以下示例,但 recommended 将 observables 与 Angular 2 一起使用。

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

在此处查看original 帖子。但是 TypeScript 不支持native ES6 Promises,如果你想使用它,你可能需要插件。

另外,这里是promises specification。

【讨论】:

这并没有解释承诺如何解决这个问题。 jQuery 和 fetch 方法也都返回承诺。我建议修改你的答案。虽然 jQuery 的不太一样(当时有,但 catch 没有)。【参考方案9】:

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

// 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:", JSON.stringify(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...)

请注意我们如何使用来自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:", JSON.stringify(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:", JSON.stringify(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 给你一个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:", JSON.stringify(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:", JSON.stringify(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 给了你一个Promise,如果你可以使用ES2017+ 语法(也许使用像Babel 这样的转译器),你可以使用async function 和for-ofawait

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:", JSON.stringify(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" pattern 的变体(这比通常的 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:", JSON.stringify(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+ arrow functions 更方便:

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:", JSON.stringify(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; 

【讨论】:

您能解释一下代码的if (--expecting === 0) 部分是如何工作的吗?您的解决方案的回调版本对我来说非常有用,我只是不明白您如何使用该语句检查已完成的响应数量。感谢这只是我缺乏知识。有没有另一种方式可以写支票? @Sarah: expectingarray.length 的值开始,这就是我们要发出多少请求。我们知道在所有这些请求开始之前不会调用回调。在回调中,if (--expecting === 0) 执行以下操作: 1. 递减 expecting(我们已收到响应,因此我们期望少一个响应)并且如果值 after 减量为 0(我们不再期待任何回应),我们完成了! @Henke - 我认为这确实是个人喜好,虽然通常我更喜欢记录原始数据并让控制台处理它,但在这种特定情况下,我认为你的改变是正确的.谢谢! :-) 为了自己(和其他人?)的方便,添加指向相关答案的链接:How to make many asynchronous calls and wait for them all。【参考方案10】:

看看这个例子:

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

【讨论】:

【参考方案11】:

这是许多新 JavaScript 框架中使用的双向数据绑定存储概念非常适合您的地方之一...

因此,如果您使用Angular、React 或任何其他执行双向数据绑定或存储概念的框架,则此问题已为您解决,简单来说,您的结果是undefined在第一阶段,所以您在收到数据之前已经获得了result = undefined,然后一旦您获得结果,它将被更新并分配给您的 Ajax 调用响应的新值...

但是,例如,如您在这个问题中所问的那样,您如何使用纯 JavaScript 或 jQuery 来做到这一点?

您可以使用回调、promise 和最近的 observable 来为您处理它。例如,在 Promise 中,我们有一些像 success()then() 这样的函数,它们会在你的数据准备好时执行。与 observable 上的回调或 subscribe 函数相同。

例如,在您使用 jQuery 的情况下,您可以执行以下操作:

$(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(); // The call happens here
);

如需更多信息,请研究 promises 和 observables,它们是执行此异步操作的新方法。

【讨论】:

这在全局范围内很好,但在某些模块上下文中,您可能希望确保回调的正确上下文,例如$.ajax(url: "api/data", success: fooDone.bind(this)); 这实际上是不正确的,因为 React 是单向数据绑定 @MatthewBrent 你没有错,但也不对,React props 是对象,如果更改,它们会在整个应用程序中更改,但这不是 React 开发人员推荐使用它的方式... 【参考方案12】:

这是我们在与 JavaScript 的“奥秘”作斗争时面临的一个非常常见的问题。今天就让我来揭开这个谜团吧。

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

function foo()
    // Do something
    return 'wohoo';


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

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

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

function foo()
    setTimeout( ()=> 
        return 'wohoo';
   , 1000)


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

所以你去;这种延迟刚刚破坏了我们预期的功能!但究竟发生了什么?好吧,如果你看一下代码,这实际上是很合乎逻辑的。

函数foo()在执行时什么都不返回(因此返回值为undefined),但它确实启动了一个计时器,该计时器在1秒后执行一个函数以返回“哇”。但是正如你所看到的,分配给 bar 的值是从 foo() 立即返回的东西,它什么都不是,即只是 undefined

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

让我们向我们的函数请求一个promise。 Promise 的真正含义是:它意味着该函数保证您提供将来获得的任何输出。所以让我们看看它在上面的小问题中的作用:

function foo()
   return new Promise((resolve, reject) =>  // I want foo() to PROMISE me something
    setTimeout ( function()
      // Promise is RESOLVED, when the 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 来处理 Promise 之外,还有另一种方法。这个想法是识别一个异步函数,然后等待承诺解决,然后再转到下一行代码。它仍然只是引擎盖下的promises,但使用了不同的语法方法。为了让事情更清楚,您可以在下面找到一个比较:

然后/赶上版本:

function saveUsers()
     getUsers()
      .then(users => 
         saveSomewhere(users);
      )
      .catch(err => 
          console.error(err);
       )
 

异步/等待版本:

  async function saveUsers()
     try
        let users = await getUsers()
        saveSomewhere(users);
     
     catch(err)
        console.error(err);
     
  

【讨论】:

这仍然被认为是从 promise 或 async/await 返回值的最佳方式吗? @edwardsmarkf 我个人认为没有最好的方法。我将 promise 与 then/catch 、 async/await 以及代码的异步部分的生成器一起使用。这在很大程度上取决于使用环境。【参考方案13】:

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

这是一个相同的例子:

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 对象来存储值。这使得即使在异步作业之后也可以获得结果。

我经常使用这种方法。我很想知道这种方法在涉及通过连续模块将结果回传的情况下效果如何。

【讨论】:

这里使用对象并没有什么特别之处。如果您将他的回复直接分配给result,它也会起作用。它之所以有效,是因为您在 异步函数完成之后读取变量。【参考方案14】:

虽然 Promise 和回调在许多情况下都可以正常工作,但表达如下内容是一件很痛苦的事情:

if (!name) 
  name = async1();

async2(name);

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

async1(name, callback) 
  if (name)
    callback(name)
  else 
    doSomething(callback)
  


async1(name, async2)

虽然在小例子中没问题,但当你有很多类似的情况和涉及错误处理时,它会变得很烦人。

Fibers 有助于解决问题。

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

您可以签出项目here。

【讨论】:

这类似于生成器函数吗? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…* 这仍然相关吗? 如果你正在使用一些最新版本的节点,你可以使用async-await。如果有人坚持使用旧版本,他们可以使用此方法。【参考方案15】:

下面我写的例子展示了如何

处理异步 HTTP 调用; 等待每个 API 调用的响应; 使用Promise 模式; 使用Promise.all 模式加入多个HTTP 调用;

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

上下文。该示例查询Spotify Web API 端点,以便为给定的一组查询字符串搜索playlist 对象:

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

对于每个项目,一个新的 Promise 将触发一个块 - ExecutionBlock,解析结果,根据结果数组安排一组新的 Promise,即 Spotify user 对象的列表并执行新的 HTTP在ExecutionProfileBlock 内异步调用。

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

注意 最近的 Spotify search API 需要在请求标头中指定访问令牌:

-H "Authorization: Bearer your access token" 

所以,要运行以下示例,您需要将访问令牌放入请求标头中:

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);
  );
&lt;div id="console" /&gt;

我已经广泛讨论了这个解决方案here。

【讨论】:

【参考方案16】:

简短的回答是,您必须像这样实现回调:

function callback(response) 
    // Here you can do what ever you want with the response object.
    console.log(response);


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

【讨论】:

【参考方案17】:

JavaScript 是单线程的。

浏览器可以分为三个部分:

    事件循环

    网络 API

    事件队列

事件循环永远运行,即一种无限循环。事件队列是你的所有函数被推送到某个事件(例如:点击)的地方。

这是从队列中逐一执行并放入事件循环中,该循环执行该函数并在执行第一个函数后为下一个函数做好准备。这意味着一个函数的执行只有在队列中的前一个函数在事件循环中执行后才会开始。

现在让我们假设我们将两个函数推送到一个队列中。一种是从服务器获取数据,另一种是利用该数据。我们先将 serverRequest() 函数推送到队列中,然后再推送 utiliseData() 函数。 serverRequest 函数进入事件循环并调用服务器,因为我们永远不知道从服务器获取数据需要多少时间,所以这个过程预计需要时间,所以我们忙于我们的事件循环从而挂起我们的页面。

这就是 Web API 发挥作用的地方。它从事件循环中获取这个函数并处理服务器使事件循环空闲,以便我们可以执行队列中的下一个函数。

队列中的下一个函数是 utiliseData() ,它进入循环,但由于没有可用数据,它会被浪费,并且下一个函数的执行会一直持续到队列末尾。 (这称为异步调用,即我们可以在获取数据之前执行其他操作。)

假设我们的 serverRequest() 函数在代码中有一个 return 语句。当我们从服务器 Web API 取回数据时,它会在队列末尾将其推送到队列中。

当它被推到队列的末尾时,我们无法利用它的数据,因为我们的队列中没有任何函数可以利用这些数据。 因此无法从异步调用中返回某些内容。

因此解决方案回调promise

来自one of the answers here 的图像 正确解释了回调的使用...*

我们将我们的函数(利用从服务器返回的数据的函数)提供给调用服务器的函数。

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

在我的代码中它被称为:

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

JavaScript.info callback

【讨论】:

【参考方案18】:

2017 回答:您现在可以在每个当前浏览器和Node.js 中完全按照您的意愿行事

这很简单:

返回一个承诺 使用'await',它会告诉 JavaScript 等待承诺被解析为一个值(如 HTTP 响应) 将'async'关键字添加到父函数

这是您的代码的工作版本:

(async function()

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

)()

await is supported in all current browsers and Node.js 8

【讨论】:

不幸的是,这仅适用于返回承诺的函数——例如,它不适用于使用回调的 Node.js API。而且我不建议在没有 Babel 的情况下使用它,因为不是每个人都使用“当前浏览器”。 @MichałPerłakowski 节点 8 包含 nodejs.org/api/util.html#util_util_promisify_original,可用于使 node.js API 返回承诺。您是否有时间和金钱来支持非当前浏览器显然取决于您的情况。 IE 11 在 2018 年仍然是当前的浏览器,遗憾的是它不支持 await/async IE11 不是当前浏览器。它是 5 年前发布的,根据 caniuse 的数据,其全球市场份额为 2.5%,除非有人将您的预算翻倍以忽略所有当前技术,否则它不值得大多数人花时间。【参考方案19】:

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

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

简单使用示例:

$http(
    method: 'get',
    url: 'google.com'
).then(function(response) 
    console.log(response);
, function(error) 
    console.log(error)
);

【讨论】:

【参考方案20】:

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

如果底层函数被promisified

nsynjs 会依次评估所有的 Promise,并将 Promise 结果放入data 属性中:

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');
);
&lt;script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"&gt;&lt;/script&gt;

如果底层功能没有被承诺

步骤 1. 将带有回调的函数包装到 nsynjs-aware 包装器中(如果它有 promisified 版本,则可以跳过此步骤):

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;

Step 2. 将同步逻辑放入函数中:

function process() 
    console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);

Step 3. 通过nsynjs同步运行函数:

nsynjs.run(process,this,function () 
    console.log("synchronous function finished");
);

Nsynjs 将逐步评估所有运算符和表达式,如果某些慢速函数的结果尚未准备好,则暂停执行。

更多例子是here。

【讨论】:

这很有趣。我喜欢它允许以其他语言编写异步调用的方式。但从技术上讲,它不是真正的 JavaScript?【参考方案21】:

ECMAScript 6 具有“生成器”,可让您轻松地以异步方式进行编程。

function* myGenerator() 
    const callback = yield;
    let [response] = yield $.ajax("https://***.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();
    

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

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

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

回调...args 被包装在一个数组中,并在您读取它们时进行解构,以便该模式可以处理具有多个参数的回调。例如node fs:

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

【讨论】:

您认为生成器/异步生成器是单独的异步 API 解决方案吗?或者你会使用生成器来包装另一个异步 API,比如 promise/defered ?我同意这是对异步世界的另一个强大补充,但仍然没有找到让我采用它们的生成器的正确用法。【参考方案22】:

我们发现自己处于一个似乎沿着我们称为“时间”的维度前进的宇宙中。我们并不真正了解时间是什么,但我们已经开发了抽象和词汇,让我们可以推理和谈论它:“过去”、“现在”、“未来”、“之前”、“之后”。

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

考虑一个例子。你打电话给送奶工,要一些牛奶。当它出现时,你想把它放在你的咖啡里。你现在不能把牛奶放进你的咖啡里,因为它还没有出现。您必须等待它来,然后才能将其放入咖啡中。换句话说,以下内容不起作用:

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

因为 JavaScript 无法知道在执行 put_in_coffee 之前它需要 等待order_milk 完成。换句话说,它不知道order_milk异步——直到未来某个时间才会产生牛奶。 JavaScript 和其他声明性语言在不等待的情况下一个接一个地执行语句。

解决这个问题的经典 JavaScript 方法,利用 JavaScript 支持函数作为可以传递的第一类对象这一事实,将函数作为参数传递给异步请求,然后它会调用异步请求它在未来的某个时间完成了它的任务。这就是“回调”方法。它看起来像这样:

order_milk(put_in_coffee);

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

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

order_milk(function(milk)  put_in_coffee(milk, drink_coffee); 

我要传递给put_in_coffee 的地方,既要放入牛奶,又要在放入牛奶后执行的动作 (drink_coffee)。这样的代码很难编写、阅读和调试.

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

var answer;
$.ajax('/foo.json') . done(function(response) 
  callback(response.data);
);

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

输入承诺

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

对于我们的牛奶和咖啡,我们设计order_milk 来返回牛奶到达的承诺,然后将put_in_coffee 指定为then 操作,如下所示:

order_milk() . then(put_in_coffee)

这样做的一个好处是我们可以将它们串在一起以创建未来发生的序列(“链接”):

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

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

function get_data() 
  return $.ajax('/foo.json');

实际上,我们所做的只是将return 添加到对$.ajax 的调用中。这是因为 jQuery 的 $.ajax 已经返回了一种类似于 promise 的东西。 (实际上,在不详细说明的情况下,我们更愿意包装这个调用以便返回一个真正的承诺,或者使用$.ajax 的替代方法。)现在,如果我们想要加载文件并等待它完成然后做某事,我们可以简单地说

get_data() . then(do_something)

例如,

get_data() .
  then(function(data)  console.log(data); );

在使用 Promise 时,我们最终会将大量函数传递给 then,因此使用更紧凑的 ES6 风格的箭头函数通常会有所帮助:

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

async 关键字

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

a();
b();

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

a() . then(b);

上面,我们说过,“JavaScript 无法知道它需要等待第一个调用完成,然后再执行第二个调用”。如果有某种方法可以告诉 JavaScript,那不是很好吗?事实证明,有--await 关键字,用于一种称为“异步”函数的特殊类型的函数中。此功能是即将推出的 ECMAScript (ES) 版本的一部分,但它已经在 Babel 等转译器中提供,前提是正确的预设。这使我们可以简单地编写

async function morning_routine() 
  var milk   = await order_milk();
  var coffee = await put_in_coffee(milk);
  await drink(coffee);

在你的情况下,你可以写类似

async function foo() 
  data = await get_data();
  console.log(data);

【讨论】:

【参考方案23】:

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

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

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

【讨论】:

虽然这可行,但并不比分配给全局变量更好。【参考方案24】:

以下是处理异步请求的一些方法:

    Browser Promise object Q - JavaScript 的 promise 库 A+ Promises.js jQuery deferred XMLHttpRequest API 使用回调概念 - 作为第一个答案中的实现

示例:jQuery 延迟实现以处理多个请求

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

【讨论】:

为什么要包含输出错误的堆栈片段?【参考方案25】:

foo() 成功内使用callback() 函数。 用这种方法试试。它简单易懂。

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

【讨论】:

【参考方案26】:

1。第一个绊脚石

对于其他许多人,我遇到的异步调用令人费解 首先。 我不记得细节了,但我可能尝试过类似的方法:

let result;

$.ajax(
  url: 'https://jsonplaceholder.typicode.com/todos/1',
  success: function (response) 
    console.log('\nInside $.ajax:');
    console.log(response);
    result = response;
  
);

console.log('Finally, the result: ' + result);
.as-console-wrapper  max-height: 100% !important; top: 0; 
<script src=
"https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

哎呀! 行的输出 console.log('Finally, the result: ' + result); 我以为会打印last,实际上是打印before 其他输出! – 它不包含结果:它只打印undefined1 怎么会?

有用的见解

我清楚地记得我第一次啊哈!关于如何理解异步的时刻 来电。 是 this comment 说:你实际上不想从回调中输出数据; 您想让您的数据需要操作进入回调! 2 这在上面的例子中很明显。 但是是否仍然可以在 异步调用之后编写代码 响应完成后处理它?

2。纯 JavaScript 和回调函数

答案是是的! - 这是可能的。 一种替代方法是在连续传递中使用 callback 函数 风格: 3

const url = 'https://jsonplaceholder.typicode.com/todos/2';

function asynchronousCall (callback) 
  const request = new XMLHttpRequest();
  request.open('GET', url);
  request.send();
  request.onload = function () 
    if (request.readyState === request.DONE) 
      console.log('The request is done. Now calling back.');
      callback(request.responseText);
    
  ;


asynchronousCall(function (result) 
  console.log('This is the start of the callback function. Result:');
  console.log(result);
  console.log('The callback function finishes on this line. THE END!');
);

console.log('LAST in the code, but executed FIRST!');
.as-console-wrapper  max-height: 100% !important; top: 0; 

注意函数asynchronousCallvoid。它什么也不返回。 相反,通过使用匿名回调函数调用 asynchronousCall (asynchronousCall(function (result) ...),这个函数执行 对结果的期望操作,但仅在请求完成后 – 当responseText 可用时。

运行上面的 sn-p 显示我可能不想编写任何代码 异步调用之后(如行 LAST in the code, but executed FIRST!)。为什么? - 因为这样的代码会 异步调用传递任何响应数据之前发生。 在比较代码输出时,这样做势必会造成混淆。

3。用.then()async/await 承诺

.then() 结构是在 6 月的 ECMA-262 第 6 版中引入的 2015,在ECMA-262 中引入了async/await 构造 2017 年 6 月第 8 版。 下面的代码仍然是纯 JavaScript,取代了老式的 XMLHttpRequestFetch4

fetch('http://api.icndb.com/jokes/random')
  .then(response => response.json())
  .then(responseBody => 
    console.log('.then() - the response body:');
    console.log(JSON.stringify(responseBody) + '\n\n');
  );

async function receiveAndAwaitPromise () 
  const responseBody =
    (await fetch('http://api.icndb.com/jokes/random')).json();
  console.log('async/await:');
  console.log(JSON.stringify(await responseBody) + '\n\n');


receiveAndAwaitPromise();
.as-console-wrapper  max-height: 100% !important; top: 0; 

如果您决定使用async/await,请注意警告 构造。请注意,在上面的 sn-p 中,两个位置需要await。 如果一开始就忘记了,就没有输出。如果忘记在 第二名,唯一的输出将是空对象, (或[object Object][object Promise])。 忘记函数的 async 前缀可能是最糟糕的—— 输出将是"SyntaxError: missing ) in parenthetical" - 没有提及 缺少 async 关键字。

4。 Promise.all – URL 数组 5

假设我们需要请求一大堆 URL。 我可以发送一个请求,等待它响应,然后发送下一个请求, 等到 it 响应,以此类推... 啊! – 这可能需要很长时间。如果我可以发送会不会更好 一次全部,然后等待最慢的时间 响应到达?

作为一个简化的例子,我将使用:

urls = ['https://jsonplaceholder.typicode.com/todos/2',
        'https://jsonplaceholder.typicode.com/todos/3']

两个 URL 的 JSON:

"userId":1,"id":2,"title":"quis ut nam facilis et officia qui",
 "completed":false
"userId":1,"id":3,"title":"fugiat veniam minus","completed":false

目标是获取一个对象数组,其中每个对象包含title 来自相应 URL 的值。

为了让它更有趣一点,我假设已经有一个 我希望 URL 结果数组(titles)成为的 names 数组 合并:

namesonly = ['two', 'three']

所需的输出是将namesonlyurls 组合成一个 对象数组

["name":"two","loremipsum":"quis ut nam facilis et officia qui",
"name":"three","loremipsum":"fugiat veniam minus"]

我已将 title 的名称更改为 loremipsum

const namesonly = ['two','three'];

const urls = ['https://jsonplaceholder.typicode.com/todos/2',
  'https://jsonplaceholder.typicode.com/todos/3'];

Promise.all(urls.map(url => fetch(url)
  .then(response => response.json())
  .then(responseBody => responseBody.title)))
  .then(titles => 
    const names = namesonly.map(value => ( name: value ));
    console.log('names: ' + JSON.stringify(names));
    const latins = titles.map(value => ( loremipsum: value ));
    console.log('latins:\n' + JSON.stringify(latins));
    const result =
      names.map((item, i) => Object.assign(, item, latins[i]));
    console.log('result:\n' + JSON.stringify(result));
  );
.as-console-wrapper  max-height: 100% !important; top: 0; 

以上所有示例都很简短,简洁地传达了异步调用的方式 可用于玩具 API。 使用小型 API 可以很好地解释概念和工作代码,但是 示例可能有点枯燥。

下一节将展示一个更实际的示例,说明 API 的可能方式 结合起来创造一个更有趣的输出。

5。如何在 Postman 6 中可视化混搭

The MusicBrainz API 有关于艺术家和乐队的信息。 例如,英国摇滚乐队 Coldplay 的请求是:http://musicbrainz.org/ws/2/artist/cc197bad-dc9c-440d-a5b5-d52ba2e14234?&fmt=json&inc=url-rels+release-groups。 JSON 响应包含 - 其中包括 - 25 个最早的专辑标题 由乐队。 此信息位于release-groups 数组中。 这个数组的开始,包括它的第一个对象是:

...
  "release-groups": [
    
      "id": "1dc4c347-a1db-32aa-b14f-bc9cc507b843",
      "secondary-type-ids": [],
      "first-release-date": "2000-07-10",
      "primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc",
      "disambiguation": "",
      "secondary-types": [],
      "title": "Parachutes",
      "primary-type": "Album"
    ,
...

这个 JSON sn-p 显示 Coldplay 的第一张专辑是 Parachutes。 它还给出了id,在本例中为1dc4c347-a1db-32aa-b14f-bc9cc507b843, 这是专辑的唯一标识符。

此标识符可用于在the Cover Art Archive API 中进行查找:http://coverartarchive.org/release-group/1dc4c347-a1db-32aa-b14f-bc9cc507b843。 7

对于每张专辑,JSON 响应都包含一些图像,其中一张是 专辑封面。 对上述请求的响应的前几行:


  "images": [
    
      "approved": true,
      "back": false,
      "comment": "",
      "edit": 22132705,
      "front": true,
      "id": 4086974851,
      "image": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851.jpg",
      "thumbnails": 
        "250": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg",
        "500": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-500.jpg",
        "1200": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-1200.jpg",
        "large": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-500.jpg",
= = >   "small": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg"
    ,
...

这里有趣的是这条线 "small": "http://coverartarchive.org/release/435fc965-9121-461e-b8da-d9b505c9dc9b/4086974851-250.jpg". 该 URL 是 Parachutes 专辑封面的直接链接。

用于创建和可视化混搭的代码

总体任务是使用 Postman 可视化所有专辑标题和前面 乐队的封面。 如何编写代码来实现这一点已经在相当多的文章中描述过 在an answer 中详细说明问题 如何在 Postman 中可视化 API 混搭? – 因此我会避免 在这里进行冗长的讨论,只提供代码和截图 结果:

const lock = setTimeout(() => , 43210);
const albumsArray = [];
const urlsArray = [];
const urlOuter = 'https://musicbrainz.org/ws/2/artist/' +
  pm.collectionVariables.get('MBID') + '?fmt=json&inc=url-rels+release-groups';
pm.sendRequest(urlOuter, (_, responseO) => 
  const bandName = responseO.json().name;
  const albums = responseO.json()['release-groups'];
  for (const item of albums) 
    albumsArray.push(item.title);
    urlsArray.push('https://coverartarchive.org/release-group/' + item.id);
  
  albumsArray.length = urlsArray.length = 15;
  const images = [];
  let countDown = urlsArray.length;
  urlsArray.forEach((url, index) => 
    asynchronousCall(url, imageURL => 
      images[index] = imageURL;
      if (--countDown === 0)  // Callback for ALL starts on next line.
        clearTimeout(lock); // Unlock the timeout.
        const albumTitles = albumsArray.map(value => ( title: value ));
        const albumImages = images.map(value => ( image: value ));
        const albumsAndImages = albumTitles.map(
          (item, i) => Object.assign(, item, albumImages[i]));
        const template = `<table>
          <tr><th>` + bandName + `</th></tr>
          #each responseI
          <tr><td>title<br><img src="image"></td></tr>
          /each
        </table>`;
        pm.visualizer.set(template,  responseI: albumsAndImages );
      
    );
  );
  function asynchronousCall (url, callback) 
    pm.sendRequest(url, (_, responseI) => 
      callback(responseI.json().images.find(obj => obj.front === true)
        .thumbnails.small); // Individual callback.
    );
  
);

结果和文档

如何下载和运行 Postman Collection

运行 Postman Collection 应该很简单。 假设您使用的是the desktop version of Postman,请执行以下操作:

    下载并保存http://henke.atwebpages.com/postman/mbid/MusicBands.pm_coll.json 放在硬盘上的合适位置。

    在 Postman 中,Ctrl + O > 上传文件 > MusicBands.pm_coll.json > 导入。 您现在应该在 Postman 的收藏中看到 MusicBands

    收藏 > MusicBands > DummyRequest > 发送8

    在 Postman 响应正文中,单击可视化

    您现在应该可以滚动 15 个相册,如 上面的截图。

参考文献

How do I return the response from an asynchronous call? Some questions and answers about asynchronous calls Using plain JavaScript and a callback function Continuation-passing style XMLHttpRequest: onload vs. onreadystatechange XMLHttpRequest.responseText An example demonstrating async/await Fetch Promise The XMLHttpRequest Standard The Fetch Standard The Web Hypertext Application Technology Working Group (WHATWG) Links to ECMA specifications Convert an array of values to an array of objects How can I fetch an array of URLs with Promise.all? Documentation of the MusicBrainz API Documentation of the Cover Art Archive API How can I visualize an API mashup in Postman?

1 原发帖人表示为:他们都回来了 undefined2 如果您认为异步调用令人困惑,请考虑使用 查看some questions and answers about asynchronous calls 看看是否有帮助。3XMLHttpRequest 的名称与中的 X 一样具有误导性 AJAX – 现在 Web API 的数据格式普遍是 JSON,而不是 XML。4Fetch 返回Promise。 我很惊讶地发现 XMLHttpRequestFetch 都不是 ECMAScript 标准。 JavaScript 可以在这里访问它们的原因是 Web 浏览器提供 他们。 The Fetch Standard 和 the XMLHttpRequest Standard 均由 the Web Hypertext Application Technology Working Group (WHATWG) 成立于 2004 年 6 月。5 本节借鉴了很多 How can I fetch an array of URLs with Promise.all?.6 本节在很大程度上依赖于 How can I visualize an API mashup in Postman?.7 此 URL 会自动重定向到: https://ia800503.us.archive.org/29/items/mbid-435fc965-9121-461e-b8da-d9b505c9dc9b/index.json.8 如果出现错误, 运行脚本时出了点问题, 尝试再次点击发送

【讨论】:

【参考方案27】:

使用承诺

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

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

用法

ajax("GET", "/test", "acrive=1").then(function(result) 
    // Code depending on result
)
.catch(function() 
    // An error occurred
);

但是等等……!

使用 Promise 有问题!

为什么要使用我们自己的自定义 Promise?

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

Uncaught ReferenceError: Promise is not defined

所以我决定为 ES3 到以下 JavaScript 编译器实现我自己的 Promise 类,如果它没有定义的话。只需将此代码添加到您的主代码之前,然后安全地使用 Promise!

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

【讨论】:

我认为你也可以使用回调 :D,但这太不可思议了。【参考方案28】:

当然有很多方法,比如同步请求、promise,但根据我的经验,我认为你应该使用回调方法。 JavaScript 的异步行为很自然。

因此,您的代码 sn-p 可以重写为有点不同:

function foo() 
    var result;

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

    return result;


function myCallback(response) 
    // Does something.

【讨论】:

回调或 JavaScript 本质上没有异步。 为什么要保留var result;return result;?后者仍将总是返回undefined!【参考方案29】:

问题是:

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

其中可以解释为:

如何使异步代码看起来同步

解决方案是避免回调,并结合使用 Promisesasync/await

我想举一个 Ajax 请求的例子。

(虽然可以用JavaScript写,但我更喜欢用Python写,用Transcrypt编译成JavaScript,就够清楚了。)

让我们首先启用 jQuery 使用,让 $ 可以作为 S 使用:

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

定义一个返回 Promise 的函数,在本例中为 Ajax 调用:

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

像使用同步一样使用异步代码:

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

【讨论】:

任何对使用async / await 感兴趣的人可能还想阅读this answer(可能还有我在它下面的评论:-)。【参考方案30】:

除了向你扔代码之外,还有两个概念是理解 JavaScript 如何处理回调和异步性的关键(这甚至是一个词吗?)

The Event Loop and Concurrency Model

您需要注意三件事; 队列; the event loop 和堆栈

从广义上讲,事件循环就像项目经理,它不断地监听任何想要运行的函数,并在队列和堆栈之间进行通信。

while (queue.waitForMessage()) 
  queue.processNextMessage();

一旦它收到一条消息来运行某些东西,它就会将它添加到队列中。队列是等待执行的事物的列表(例如您的 AJAX 请求)。想象一下:

    使用 foobarFunc 调用 foo.com/api/bar 去执行一个无限循环 ...等等

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

function foobarFunc (var) 
  console.log(anotherFunction(var));

所以 foobarFunc 需要执行的任何东西(在我们的例子中是 anotherFunction)都会被压入堆栈。执行,然后忘记 - 事件循环将移动到队列中的下一个事物(或侦听消息)

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

什么时候运行

当您使用 AJAX 向外部方进行调用或运行任何异步代码(例如 setTimeout)时,JavaScript 依赖于响应才能继续。

最大的问题是它什么时候会得到响应?答案是我们不知道——所以事件循环正在等待该消息说“嘿,快跑吧”。如果 JavaScript 只是同步地等待该消息,您的应用程序将冻结并且它会很糟糕。因此 JavaScript 继续执行队列中的下一项,同时等待消息被添加回队列。

这就是我们使用称为回调的异步功能的原因。 - 一个函数或处理程序,当传递给另一个函数时,将在以后执行。 promise 使用回调(例如,传递给 .then() 的函数)作为一种以更线性的方式推理这种异步行为的方法。承诺是一种表达“我承诺在某个时候返回一些东西”的方式,而回调是我们处理最终返回的值的方式。 jQuery 使用称为 deffered.done deffered.faildeffered.always (以及其他)的特定回调。大家可以看到here

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

因为回调不会立即执行,但稍后将引用传递给未执行的函数很重要。所以

function foo(bla) 
  console.log(bla)

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

希望这会有些道理。当您遇到类似这样令人困惑的事情时 - 我强烈建议您完整阅读文档以至少了解它。它会让你成为一个更好的开发者。

【讨论】:

我很难接受“回调有点像承诺”。这就像说“面粉有点像面包”,但事实并非如此。您使用面粉、水和其他配料,混合它们,最终经过一个过程,面包就是结果。 这是真的——我想我试图说一些不太明白我的意思的话。 JS 中的 promise 显然代表了与回调不同的东西,但是在编写任何类型的异步功能时,您将执行回调。 Promise 代表值,但回调是我们需要使用该值在未来某个时间返回时执行的操作。 如果没有回调来对解析的值做某事,承诺几乎是无用的(但并非总是如此)

以上是关于如何从异步调用返回响应的主要内容,如果未能解决你的问题,请参考以下文章

如何从异步调用返回响应

如何从异步调用返回响应

如何从异步调用返回响应

如何从异步调用返回响应

如何从异步调用返回响应

如何从异步调用返回响应