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

How do I return the response from an asynchronous call?

提问人:Felix Kling 提问时间:1/9/2013 最后编辑:yuriy636Felix Kling 更新时间:10/31/2023 访问量:2095168

问:

如何从发出异步请求的函数返回响应/结果?foo

我试图从回调中返回值,以及将结果分配给函数内部的局部变量并返回该变量,但这些方法都没有真正返回响应——它们都返回或变量的初始值。undefinedresult

接受回调的异步函数示例(使用 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`
}

使用 promise 的 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`
}
JavaScript AJAX 异步

评论

0赞 Sunil Kumar 9/8/2021
像这样使用 deasync stackoverflow.com/a/47051880/2083877
17赞 Seblor 9/10/2021
@SunilKumar我认为这没有用。OP 提出了这个问题和自我回答,以记录如何从异步调用中获取响应。建议第三方模块违背了这一目的,IMO 该模块引入的范式不是好的做法。
6赞 Felix Kling 10/15/2021
@Liam:这只是一个接受回调的异步函数的示例。
0赞 Trishant Saxena 7/27/2023
Promise 提供了一种更简洁、更结构化的方式来处理异步操作。您可以创建表示异步调用结果的 promise。承诺可以通过响应来解析,也可以通过错误被拒绝。

答:

6607赞 Felix Kling 1/9/2013 #1

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

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

问题

Ajax 中的 A 代表异步。这意味着发送请求(或者更确切地说是接收响应)被排除在正常执行流程之外。在您的示例中,立即返回,并在调用您作为回调传递的函数之前执行下一个语句 。$.ajaxreturn result;success

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

同步

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

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

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

var item = findItem();

// Do something with item
doSomethingElse();

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

异步

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

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

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

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


解决方案

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

你问为什么不好?

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

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

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

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

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


ES2017+: 使用 async/await 的 promise

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

async/await建立在 promise 之上:函数总是返回一个 promise。 “解开”一个 Promise,并导致解析 Promise 时所用的值,或者在 Promise 被拒绝时引发错误。asyncawait

重要:您只能在函数内部或 JavaScript 模块中使用。模块之外不支持顶级,因此如果不使用模块,您可能必须创建异步 IIFE(立即调用的函数表达式)来启动上下文。awaitasyncawaitasync

你可以在 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 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);
})();

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


让函数接受回调

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

在问题的示例中,您可以接受回调并将其用作回调。所以这个foosuccess

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将引用我们在调用它时传递给的函数,并将其传递给 。即一旦 Ajax 请求成功,将调用并将响应传递给回调(可以用 引用,因为这就是我们定义回调的方式)。foosuccess$.ajaxcallbackresult

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

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

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

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

下面是使用 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).
  });
.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 的延迟对象

延迟对象是 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 和 deferted 对象只是未来值的容器,它们不是值本身。例如,假设您有以下内容:

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
}

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

但解决方法很简单:

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

jQuery的

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

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

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

小心!无法发出同步 JSONP 请求。就其本质而言,JSONP 始终是异步的(甚至不考虑此选项的另一个原因)。

评论

95赞 Felix Kling 1/17/2013
@Pommy:如果你想使用jQuery,你必须包含它。请参阅 docs.jquery.com/Tutorials:Getting_Started_with_jQuery
23赞 cssyphus 2/7/2013
在解决方案 1 中,sub jQuery,我无法理解这一行:(是的,我意识到我的昵称在这种情况下有点讽刺)If you use any other jQuery AJAX method, such as $.get, $.getJSON, etc., you have them to $.ajax.
47赞 Felix Kling 2/7/2013
@gibberish:嗯,我不知道怎么才能说得更清楚。你看到如何调用并将函数传递给它()吗? 在此函数内部使用,是 Ajax 请求的响应。为了引用此函数,调用并赋值 foo 的第一个参数,而不是匿名函数。因此,将在请求成功时调用。我试着再解释一下。foofoo(function(result) {....});resultcallbacksuccess$.ajaxcallback
57赞 Chris Moschini 4/16/2013
这个问题的聊天已经死了,所以我不确定在哪里提出概述的更改,但我建议:1)将同步部分更改为简单的讨论,说明为什么它不好,没有代码示例来说明如何做到这一点。2) 删除/合并回调示例以仅显示更灵活的 Deferred 方法,我认为对于那些学习 Javascript 的人来说,这也可能更容易遵循。
23赞 Felix Kling 10/9/2015
@Jessi:我想你误解了答案的那部分。如果希望 Ajax 请求是同步的,则不能使用。但是,您不应希望请求是同步的,因此这不适用。您应该使用回调或承诺来处理响应,如答案前面所述。$.getJSON
1221赞 Benjamin Gruenbaum 5/30/2013 #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在为使用jQuery for AJAX的人编写答案方面做得很好,但我决定为那些不使用jQuery的人提供另一种选择。

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


你面对的是什么

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

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

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

这里有一个简单的类比:

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

(小提琴)

返回的值是由于该部件尚未执行。AJAX 的行为是这样的,在服务器有机会告诉你的浏览器该值是什么之前,你就返回了该值。aundefineda=5

这个问题的一个可能的解决方案是 被动地编码 ,告诉程序在计算完成后该做什么。

function onComplete(a){ // When the code completes, do this
    alert(a);
}

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

这称为 CPS。基本上,我们传递一个操作,以便在它完成时执行,我们告诉代码如何在事件完成时做出反应(例如我们的 AJAX 调用,或者在本例中超时)。getFive

用法为:

getFive(onComplete);

这应该提醒“5”到屏幕上。(小提琴)。

可能的解决方案

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

  1. 使 AJAX 调用同步(我们称之为 SJAX)。
  2. 重构代码以正确处理回调。

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

至于同步AJAX,就不要这样做了!菲利克斯的回答提出了一些令人信服的论点,说明为什么这是一个坏主意。总而言之,它会冻结用户的浏览器,直到服务器返回响应并造成非常糟糕的用户体验。以下是另一个摘自 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. 重构代码

让函数接受回调。在示例中,代码可以接受回调。我们将告诉我们的代码在完成后如何做出反应foofoo

所以:

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

评论

32赞 Matthew G 8/16/2013
“同步请求会阻止代码的执行,并可能泄漏内存和事件” 同步请求如何泄漏内存?
453赞 cocco 8/19/2013 #3

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 变量名称):

最简单的:

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 高级功能

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

OnReadyStateChange 仅在要获取状态 2 上的标头时才有用。

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


现在,如果您想使用 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传递,它也不会抛出错误。

在第一种情况下,错误位于 under 中。displayAjax()this.statusTextMethod not Allowed

在第二种情况下,它只是工作。您必须在服务器端检查是否传递了正确的帖子数据。

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

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

只有 which 被设置为错误this.type

如果您完全无法控制错误,为什么要添加错误处理程序? 大多数错误都是在回调函数中返回的。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 做很多事情:

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

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

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

评论

22赞 Benjamin Gruenbaum 8/23/2013
虽然这个答案很好(我们都喜欢 XHR2,发布文件数据和多部分数据真是太棒了)——这显示了使用 JavaScript 发布 XHR 的语法糖——你可能想把它放在博客文章中(我想要它)甚至放在库中(不确定名称,或者可能更好:))。我不明白它如何解决从 AJAX 调用返回响应的问题。(有人仍然可以这样做,但不明白为什么它;)不起作用)。顺便说一句 - 如果你从方法中返回,这样用户就可以挂钩等,那就太酷了。xajaxxhrvar res = x("url")cerror
33赞 Benjamin Gruenbaum 8/24/2013
2.ajax is meant to be async.. so NO var res=x('url')..这就是这个问题的重点,答案:)
21赞 stone 10/8/2017
@cocco 所以你在 SO 答案中写了误导性的、不可读的代码,以节省一些击键?请不要那样做。
264赞 Hemant Bavle 2/19/2014 #4

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

评论

11赞 Hemant Bavle 3/29/2014
我不知道是谁投了反对票。但这是一个有效的解决方法,实际上我使用这种方法来创建一个完整的应用程序。jquery.ajax 不返回数据,因此最好使用上述方法。如果错了,请解释并提出更好的方法。
20赞 Benjamin Gruenbaum 4/10/2014
对不起,我忘了发表评论(我通常会发表评论!我投了反对票。反对票并不表示事实正确或缺乏,它们表示在上下文中有用或缺乏。鉴于菲利克斯的答案已经更详细地解释了这一点,我发现你的答案没有用。顺便说一句,如果响应是 JSON,为什么要字符串化响应?
11赞 Hemant Bavle 4/10/2014
还行。。@Benjamin,我使用 stringify 将 JSON 对象转换为字符串。感谢您澄清您的观点。将牢记发布更详细的答案。
288赞 Nic 5/23/2014 #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);
    }
});

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

评论

21赞 Jacques 1/4/2016
这个答案完全是语义上的......你的成功方法只是回调中的回调。你可以拥有,它会起作用。success: handleData
183赞 Maleen Abewardana 8/26/2014 #6

角度 1

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

这里说,

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

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

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

  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 中,请看以下示例,但建议在 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));
}

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

此外,这里是 promise 规范

评论

20赞 Benjamin Gruenbaum 11/4/2014
不过,这并不能解释承诺将如何解决这个问题。
9赞 Tracker1 2/20/2015
jQuery 和 fetch 方法也都返回 promise。我建议修改你的答案。虽然jQuery的并不完全相同(那么就在那里,但catch不是)。
374赞 Benjamin Gruenbaum 5/12/2015 #7

如果你使用的是 promises,这个答案是给你的。

这意味着AngularJS,jQuery(延迟),本机XHR的替换(fetch),Ember.js,Backbone.js的保存或任何返回promise的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为使用jQuery和Ajax回调的人编写了一个很好的答案。我对原生 XHR 有一个答案。此答案适用于前端或后端 promise 的一般用法。


核心问题

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

每当调用返回 promise 的方法时,处理程序始终异步执行 - 即,在它们下面的代码之后执行,这些代码不在处理程序中。then.then

这意味着,当您返回已定义的处理程序时,尚未执行。这反过来意味着您返回的值尚未及时设置为正确的值。datathen

以下是该问题的简单类比:

    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

的值是,因为该部件尚未执行。它可能会在一秒钟内执行,但到那时它与返回的值无关。dataundefineddata = 5

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

这个问题的一个可能的解决方案是被动地编码,告诉程序在计算完成后要做什么。Promise 本质上是时间性的(时间敏感的),从而积极地实现了这一点。

快速回顾承诺

Promise 是随时间推移的价值。承诺是有状态的。它们开始时是挂起的,没有值,可以解决到:

  • 已实现表示计算成功完成。
  • rejected 表示计算失败。

一个承诺只能改变一次状态,之后它将永远保持相同的状态。您可以将处理程序附加到 promise 以提取其值并处理错误。 处理程序允许链接调用。Promise 是使用返回 Promise 的 API 创建的。例如,更现代的 Ajax 替换或 jQuery 的返回承诺。thenthenfetch$.get

当我们调用一个 promise 并从中返回一些东西时,我们得到了对处理后值的承诺。如果我们再回一个承诺,我们会得到令人惊奇的东西,但让我们牵住我们的马。.then

有承诺

让我们看看如何用 promise 解决上述问题。首先,让我们通过使用 Promise 构造函数创建延迟函数来演示我们对 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, and we can't access the plain value
getFive().then(function(five){
   document.body.innerHTML = five;
});

基本上,我们不是返回一个由于并发模型而无法执行的值 - 而是返回一个我们可以用 解开包装的值的包装器。这就像一个盒子,你可以用.thenthen

应用这个

这与原始 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, Nodes.js, and io.js only
    yield 1;
    yield 2;
    while(true) yield 3;
}

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

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

这是一个有点棘手但非常强大的技巧,让我们以同步方式编写异步代码。有几个“跑步者”可以为您做到这一点。编写一个代码是短短的几行代码,但它超出了这个答案的范围。我将在这里使用 Bluebird,但还有其他包装器,例如 或 .Promise.coroutinecoQ.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 中,这进一步标准化。现在有几个建议,但在所有这些建议中,你都可以承诺。这只是上面 ES6 提案的“糖”(更好的语法),通过添加 和 关键字。举个上面的例子:awaitasyncawait

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
}

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

评论

0赞 DWB 4/17/2023
简而言之,异步函数可以:1)将无意义的“待处理”承诺或“未定义”变量分配给同步流(常见陷阱) 2)从已实现的承诺中提取响应正文,用于自己的或回调处理 3)将承诺从“链”上返回给另一个调用异步函数
115赞 jsbisht 9/2/2015 #8

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

下面是一个相同的示例:

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

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

评论

12赞 Felix Kling 9/2/2015
在这里使用对象没有什么特别之处。如果您直接将他的响应分配给 ,它也会起作用。它之所以有效,是因为您在异步函数完成后读取变量。result
42赞 David R Tribble 9/24/2015 #9

简短的回答:您的方法会立即返回,而调用会在函数返回后异步执行。那么问题就在于异步调用返回后如何或在哪里存储异步调用检索到的结果。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

评论

15赞 Felix Kling 9/24/2015
虽然这可行,但它并不比分配给全局变量更好。
44赞 5 revs, 4 users 87%user663031 #10

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

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

请看一个例子。你打电话给送奶工,点一些牛奶。当它到来时,你想把它放在你的咖啡里。你现在不能把牛奶放在咖啡里,因为它还没有到来。你必须等待它到来,然后才能把它放进你的咖啡里。换言之,以下操作将不起作用:

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

因为JavaScript没有办法知道它需要等待完成才能执行。换句话说,它不知道这是异步的——这是直到未来的某个时候才会产生牛奶的东西。JavaScript 和其他声明性语言执行一个又一个语句,无需等待。order_milkput_in_coffeeorder_milk

解决这个问题的经典 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_coffeedrink_coffee

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

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

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

输入承诺

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

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

order_milk() . then(put_in_coffee)

这样做的一个优点是,我们可以将它们串在一起,以创建未来出现的序列(“链接”):

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

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

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

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

get_data() . then(do_something)

例如

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

当使用 promise 时,我们最终会将许多函数传递给 ,因此使用更紧凑的 ES6 样式箭头函数通常很有帮助:then

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

关键字async

但是,如果同步,则必须以一种方式编写代码,如果异步,则必须以一种完全不同的方式编写代码,这仍然有些令人不满意。对于同步,我们编写

a();
b();

但是如果是异步的,我们必须用 promise 来写a

a() . then(b);

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

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);
}
101赞 rohithpr 1/26/2016 #11

虽然 promise 和回调在许多情况下都很好用,但要表达这样的东西是很痛苦的:

if (!name) {
  name = async1();
}
async2(name);

你最终会经历;检查是否未定义,并相应地调用回调。async1name

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
}

您可以在此处查看该项目。

评论

3赞 Emanegux 6/7/2017
这与生成器函数类似吗?developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/......*
3赞 Aluan Haddad 3/19/2018
这仍然相关吗?
2赞 rohithpr 3/20/2018
如果您使用的是某些最新版本的 node,则可以使用。如果有人坚持使用旧版本,他们可以使用此方法。async-await
97赞 loretoparisi 4/13/2016 #12

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

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

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

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

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

对于每个项目,一个新的 Promise 将触发一个块 - ,解析结果,根据结果数组(即 Spotify 对象列表)调度一组新的 Promise,并在异步中执行新的 HTTP 调用。ExecutionBlockuserExecutionProfileBlock

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

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

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

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

96赞 Pablo Matias Gomez 4/22/2016 #13

简短的回答是,您必须实现如下回调:

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

$.ajax({
    url: "...",
    success: callback
});
74赞 Vinoth Rajendran 5/26/2016 #14

您可以使用此自定义库(使用 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)
});
129赞 Francisco Carmona 6/2/2016 #15

请看这个例子:

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

如您所见,正在返回已解决的承诺(返回时已解决)。因此,您等到 $http.get 请求完成,然后执行 console.log(res.joke)(作为正常的异步流)。getJokeres.data.value

这是 plnkr:

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

ES6 方式 (async - await)

(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);
  });
})();
282赞 Johannes Fahrenkrug 8/11/2016 #16

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

enter image description here

评论

47赞 Shaiju T 11/1/2016
一张图片胜过千言万语,人 A - 询问人 B 的详细信息来修理他的汽车,反过来,人 B - 拨打 Ajax 电话并等待服务器对汽车修理细节的响应,当收到响应时,Ajax Success 函数调用人 B 函数并将响应作为参数传递给它,人 A 收到答案。
24赞 Hassan Baig 2/5/2018
如果您在每张图像中添加代码行来说明概念,那就太好了。
12赞 barrypicker 10/23/2019
与此同时,开车的人被困在路边。他要求在继续之前将汽车修好。他现在独自一人在路边等着......他宁愿在电话里等待状态变化,但机械师不会这样做......机械师说他必须继续工作,不能简单地打电话。机械师答应他会尽快给他回电话。大约 4 小时后,这家伙放弃了并打电话给 Uber。- 超时示例。
0赞 Dan Narsavage 4/11/2021
但是有了回调功能,我觉得最后一帧左边的人被迫给对方电话号码。相反,他们必须告诉对方,“这是我想用电话里那个家伙的信息做的一切。做所有这些事情,永远不要告诉我。我错过了什么?
2赞 Johannes Fahrenkrug 4/13/2021
@FingLixon 无论如何,这都不是一部完美的漫画:-D。第二张图片应该说明当您尝试过早读取值(在回调发生之前)时会发生什么。第三张图片说明了如何设置回调方法: 左边的人基本上是回调处理程序:一旦信息可用,他就会被调用,然后可以随心所欲地使用它。我现在认为在这部漫画中有两个电话是个坏主意:给商店打电话和给左边的人打电话。我应该简化它,对不起。
43赞 Mohan Dere 8/13/2016 #17

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

  1. 浏览器 Promise 对象
  2. Q - JavaScript 的 promise 库
  3. A+ 承诺.js
  4. jQuery 延迟
  5. XMLHttpRequest 接口
  6. 使用回调概念 - 作为第一个答案中的实现

示例: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();

评论

0赞 Henke 5/9/2021
为什么要包含输出错误的堆栈代码段?
41赞 Mahfuzur Rahman 4/24/2017 #18

在 success 中使用函数。 以这种方式尝试。它简单易懂。callback()foo()

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();
183赞 T.J. Crowder 5/4/2017 #19

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

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

(我们可以取消预期,只使用 results.length === theArray.length,但这让我们有可能在调用未完成时更改 theArray......

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

但是,如果您需要从函数返回这些结果,该怎么办?正如其他答案所指出的,你不能;你必须让你的函数接受并调用回调(或返回一个 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 来拒绝 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([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 创建一个返回 promise 的包装器,然后执行以下操作......

如果给了你一个 Promise,你可以使用 Promise.alldoSomethingAsync

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

如果你知道这会忽略第二个和第三个参数,你可以直接把它传递给(用三个参数调用它的回调,但大多数人大多数时候只使用第一个):doSomethingAsyncmapmap

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 构建一个包装器,它给你一个承诺,然后执行以下操作......

如果给你一个 Promise,如果你可以使用 ES2017+ 语法(也许使用像 Babel 这样的转译器),你可以将 async 函数for-ofawait 一起使用:doSomethingAsync

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”模式的变体(这比通常的 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+ 箭头函数不那么麻烦:

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

评论

5赞 Sarah 5/28/2017
你能解释一下代码的一部分是如何工作的吗?您的解决方案的回调版本对我来说效果很好,我只是不明白您如何使用该语句检查已完成的响应数量。感谢这只是我缺乏知识。有没有其他方法可以写支票?if (--expecting === 0)
5赞 T.J. Crowder 5/29/2017
@Sarah:从 的值开始,即我们将要发出的请求数。我们知道,在所有这些请求都启动之前,不会调用回调。在回调中,执行以下操作:1.递减(我们已收到响应,因此我们预计会少一个响应),如果递减的值为 0(我们预计不会再有任何响应),则完成!expectingarray.lengthif (--expecting === 0)expecting
2赞 T.J. Crowder 5/6/2021
@Henke - 我认为这确实是个人偏好,虽然通常我更愿意记录原始数据并让控制台处理它,但在这种特定情况下,我认为您对更改的看法是正确的。谢谢!:-)
2赞 Henke 6/1/2021
为了方便我自己(和其他人?),添加了一个指向相关答案的链接:如何进行许多异步调用并等待它们
127赞 Alireza 5/24/2017 #20

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

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

但是,例如,如何在纯JavaScript或jQuery中做到这一点,例如,正如您在这个问题中提出的问题一样?

您可以使用回调、promise 和 recently observable 来为您处理它。例如,在 promise 中,我们有一些函数,例如 or,当您的数据准备好时,将执行这些函数。与可观察对象上的回调或订阅函数相同。success()then()

例如,在你使用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
});

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

评论

3赞 steve.sims 7/24/2017
这在全局范围内很好,但在某些模块上下文中,您可能希望确保回调的正确上下文,例如$.ajax({url: "api/data", success: fooDone.bind(this)});
13赞 Matthew Brent 5/4/2018
这实际上是不正确的,因为 React 是单向数据绑定
3赞 Alireza 5/14/2018
@MatthewBrent你没有错,但也不是对的,React 道具是对象,如果改变,它们会在整个应用程序中改变,但这不是 React 开发人员推荐使用它的方式......
74赞 amaksr 5/27/2017 #21

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

如果基础函数是有前途的

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

如果基础函数没有被承诺

步骤 1。使用回调将函数包装到 nsynjs-aware 包装器中(如果它有一个承诺版本,你可以跳过这一步):

var ajaxGet = function (ctx,url) {
    var res = {};
    var ex;
    $.ajax(url)
    .done(function (data) {
        res.data = data;
    })
    .fail(function(e) {
        ex = e;
    })
    .always(function() {
        ctx.resume(ex);
    });
    return res;
};
ajaxGet.nsynjsHasCallback = true;

第2步。将同步逻辑放入函数中:

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

第 3 步。通过 nsynjs 以同步方式运行函数:

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

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

更多示例在这里。

评论

4赞 J Morris 6/17/2017
这很有趣。我喜欢它允许像使用其他语言一样编写异步调用的方式。但从技术上讲,它不是真正的 JavaScript?
89赞 mikemaccana 6/2/2017 #22

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

这很简单:

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

下面是代码的工作版本:

(async function(){

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

})()

当前所有浏览器和 Node.js 8 都支持 await

评论

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

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

因此,您的代码片段可以重写为略有不同:

function foo() {
    var result;

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

    return result;
}

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

评论

6赞 Aluan Haddad 3/19/2018
回调或 JavaScript 本身并没有什么异步的。
0赞 Henke 5/9/2021
为什么要保留和?后者仍然会永远回来!var result;return result;undefined
17赞 Haim Zamir 10/25/2017 #24

在看树木之前,让我们先看看森林。

这里有许多内容丰富的答案,细节丰富,我不会重复任何一个。在 JavaScript 中编程的关键是首先拥有整体执行的正确心智模型

  1. 您的入口点是作为事件的结果执行的。为 例如,将带有代码的脚本标记加载到浏览器中。 (因此,这就是为什么您可能需要关注 页面是否准备好运行您的代码(如果它需要 DOM 元素) 先建等)
  2. 您的代码将执行到完成 - 无论有多少异步调用它 makes--不执行任何回调,包括 XHR 请求、设置超时、DOM 事件处理程序等。等待执行的每个回调都将位于队列中,等待在触发的其他事件全部完成执行后轮到它们运行。
  3. 对 XHR 请求的每个单独回调、设置超时或 DOM 调用后,该事件将运行到完成。

好消息是,如果你很好地理解了这一点,你就永远不必担心比赛条件。首先应该考虑如何将代码组织为对不同离散事件的响应,以及如何将它们串联成一个逻辑序列。您可以使用 promise 或更高级别的新 async/await 作为工具来实现此目的,也可以自己滚动。

但是,在你对实际问题领域感到满意之前,你不应该使用任何战术工具来解决问题。绘制这些依赖项的映射,以了解何时需要运行的内容。尝试对所有这些回调采取临时方法并不能很好地为您服务。

124赞 Anish K. 11/1/2017 #25

这是我们在与 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

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

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

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

让我们向我们的函数请求一个承诺。 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

UPDATE (带有 async/await 的 Promise)

除了使用 to work with promises,还有另一种方法。这个想法是识别一个异步函数,然后等待承诺解析,然后再移动到下一行代码。它仍然只是在引擎盖下,但采用了不同的语法方法。为了更清楚起见,您可以在下面找到比较:then/catchpromises

then/catch 版本:

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

async/await 版本:

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

评论

0赞 edwardsmarkf 9/27/2018
这是否仍然被认为是从 promise 或 async/await 返回值的最佳方式?
8赞 Anish K. 10/4/2018
@edwardsmarkf 就我个人而言,我认为没有最好的方法。我使用 promise 和 then/catch 、async/await 以及生成器来处理代码的异步部分。这很大程度上取决于使用的上下文。
32赞 Pieter Jan Bonestroo 1/14/2018 #26

问题是:

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

可以解释为:

如何使异步代码看起来是同步的?

解决方案是避免回调,并使用 Promiseasync/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")

评论

0赞 Henke 5/11/2021
任何有兴趣使用 / 的人可能也想阅读这个答案(可能还有我在下面的评论:-)。asyncawait
33赞 Fernando Carvajal 1/24/2018 #27

使用 ES2017 时,您应该将其作为函数声明。

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

并像这样执行它。

(async function() {
  try {
    var result = await foo()
    console.log(result)
  } catch (e) {}
})()

或者 Promise 语法。

foo().then(response => {
  console.log(response)

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

})

演示上述代码的堆栈代码段。

// The function declaration:
async function foo() {
  var response = await $.ajax({
    url: 'https://jsonplaceholder.typicode.com/todos/1'
  })
  return response;
}

// Execute it like this:
(async function() {
  try {
    var result = await foo()
    console.log(result)
  } catch (e) {}
})()

// Or use Promise syntax:
foo().then(response => {
  console.log(response)
}).catch(error => {
  console.log(error)
})
.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>

评论

0赞 Zum Dummi 2/17/2019
第二个功能可以重用吗??
0赞 Ken Ingram 6/29/2019
如果调用 oncolse,log,您如何使用结果?到那时,不是所有东西都进入了控制台吗?
0赞 Henke 5/11/2021
这是一个很好且有用的答案,它清楚地演示了如何正确使用 - 对偶性功能。需要注意的一点是,函数中实际上不需要 。(删除它们,代码仍然运行良好。这是因为返回一个 Promise,只要接收 Promise 的代码等待它,一切都会好起来的。~ * ~ * * ~ * ~ 注意:- 功能是在 2017 年 6 月的 ECMA-262 第 8 版中引入的。asyncawaitasyncawaitfoo()foo()asyncawait
0赞 RAUSHAN KUMAR 9/21/2021
我要求在从回调函数计算后返回一些数据。我该怎么做
0赞 CherryDT 10/29/2021
这是不可能的。
92赞 Aniket Jha 2/3/2018 #28

JavaScript 是单线程的。

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

  1. 事件循环

  2. Web API

  3. 事件队列

事件循环永远运行,即无限循环。在事件队列中,所有函数都会推送到某个事件(例如:单击)上。

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

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

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

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

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

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

因此,解决这个问题的办法回调承诺

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

Callback

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 回调

46赞 James 2/17/2018 #29

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

function* myGenerator() {
    const callback = yield;
    let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback});
    console.log("response is:", response);

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

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

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

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

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

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

评论

0赞 Eva Cohen 1/18/2021
您是否认为生成器/异步生成器只是一个异步 API 解决方案?或者你会使用生成器来包装另一个异步 API,如 promise/deffered ?我同意它是异步宇宙的另一个强大补充,但仍然没有找到正确的生成器用法来让我采用它们。
30赞 Matthew Brent 5/4/2018 #30

与其向你抛出代码,不如说有两个概念是理解 JavaScript 如何处理回调和异步性的关键(这甚至是一个词吗?

事件循环和并发模型

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

简单来说,事件循环就像项目经理一样,它不断侦听任何想要运行的函数,并在队列和堆栈之间进行通信。

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

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

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

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

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

因此,foobarFunc 需要执行的任何内容(在我们的例子中)都会被推送到堆栈上。执行,然后被遗忘 - 事件循环将移动到队列中的下一件事(或侦听消息)anotherFunction

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

什么时候会运行某些东西

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

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

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

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

由于回调不会立即执行,而是在以后执行,因此将引用传递给函数而不是执行的函数非常重要。所以

function foo(bla) {
  console.log(bla)
}

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

希望这是有道理的。当您遇到这样看起来令人困惑的事情时,我强烈建议您完整阅读文档,至少要理解它。它会让你成为一个更好的开发人员。

评论

0赞 Eva Cohen 1/18/2021
我正在努力接受“回调有点像承诺”。这就像说“面粉有点像面包”,但事实并非如此。 你使用面粉、水和其他添加剂,将它们混合在一起,最终经过一个过程,面包就是结果。
1赞 Matthew Brent 1/18/2021
这是真的——我想我试图说一些不太理解我的意思的话。JS 中的 promise 显然表示与回调不同的东西,但是在编写任何类型的异步功能时,您将执行回调。promise 表示值,但回调是我们需要在将来的某个时间点,当它返回时对该值执行某些操作。
1赞 Matthew Brent 1/18/2021
如果没有回调来对解析的值执行某些操作,promise 大多是无用的(但并非总是如此)
26赞 Alex Montoya 7/13/2018 #31

下面是一个有效的示例:

const validateName = async userName => {
  const url = "https://jsonplaceholder.typicode.com/todos/1";
  try {
    const response = await axios.get(url);
    return response.data
  } catch (err) {
    return false;
  }
};

validateName("user")
 .then(data => console.log(data))
 .catch(reason => console.log(reason.message))
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src=
"https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>

评论

0赞 Henke 5/11/2021
另一个很好的答案演示了 - 的用法。~ * ~ - 功能于 2017 年 6 月在 ECMA-262 第 8 版中引入。asyncawaitasyncawait
35赞 Amir Fo 12/7/2018 #32

使用 Promise

这个问题最完美的答案是使用 .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?

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

未捕获的 ReferenceError:未定义 Promise

因此,我决定在未定义的情况下,将我自己的 ES3 Promise 类实现到下面的 JavaScript 编译器中。只需在主代码之前添加此代码,然后安全地使用 Promise!

if(typeof Promise === "undefined"){
    function _typeof(obj) { "@babel/helpers - typeof"; return 

    _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
    function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
    function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
    function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
    function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
    function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
    function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
    function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
    function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
    function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
    function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
    var Promise = /*#__PURE__*/function () {
  "use strict";

  function Promise(main) {
    _classCallCheck(this, Promise);
    this.main = main;
    this.mainExecuted = false;
    this.resolved = false;
    this.rejected = false;
    this.promiseChain = [];
    this.handleError = function () {};
    this.onResolve = this.onResolve.bind(this);
    this.onReject = this.onReject.bind(this);
  }
  _createClass(Promise, [{
    key: "then",
    value: function then(handleSuccess) {
      if (this.resolved) {
        if (!this.rejected) {
          this.args = handleSuccess(this.args);
        }
      } else {
        this.promiseChain.push(handleSuccess);
        this.main(this.onResolve, this.onReject);
        this.thenExecuted = true;
      }
      return this;
    }
  }, {
    key: "catch",
    value: function _catch(handleError) {
      this.handleError = handleError;
      if (!this.mainExecuted) {
        this.main(this.onResolve, this.onReject);
        this.thenExecuted = true;
      }
      return this;
    }
  }, {
    key: "onResolve",
    value: function onResolve() {
      var _this = this;
      this.resolved = true;
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      this.args = args;
      try {
        this.promiseChain.forEach(function (nextFunction) {
          _this.args = nextFunction.apply(void 0, _toConsumableArray(_this.args));
        });
      } catch (error) {
        this.promiseChain = [];
        this.onReject(error);
      }
    }
  }, {
    key: "onReject",
    value: function onReject(error) {
      this.rejected = true;
      this.handleError(error);
    }
  }]);
  return Promise;
}();
}

评论

1赞 Alaska 9/29/2021
我想你也可以使用回调:D,但这太不可思议了。
0赞 TamusJRoyce 2/22/2023
Core-JS 存在于 2018 年。没有理由发明自己的承诺。反正看起来很酷。
4赞 Murtaza Hussain 4/17/2019 #33

与像 Babel 这样的转译器一起使用,使其在较旧的浏览器中工作。您还必须从 npm 安装此 Babel 预设和 polyfill: 。async/awaitnpm i -D babel-preset-env babel-polyfill

function getData(ajaxurl) { 
  return $.ajax({
    url: ajaxurl,
    type: 'GET',
  });
};

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

test();

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

getData(ajaxurl).then(function(res) {
    console.log(res)
}
23赞 Kamil Kiełczewski 6/11/2019 #34

等待

请求以异步方式工作,因此无法像在典型代码中那样同步读取数据。但是,使用 you 可以创建异步代码,它看起来与通常的同步/顺序样式接近/相似。处理响应数据的代码需要由一个函数包装(在下面的代码片段中),并且您需要在其中添加关键字 before(这也使用 )。async/awaitasyncloadawaitfoo()async/await

async function foo() {
  var url = 'https://jsonplaceholder.typicode.com/todos/1';
  var result = (await fetch(url)).text(); // Or .json()
  return result;
}

async function load() {
  var result = await foo();
  console.log(result);
}

load();

请记住,函数总是(隐式)将其结果包装成一个 promise(因此它返回一个 promise)。async

评论

1赞 Henke 5/10/2021
不错的答案!显然,- 构造是在 2017 年 6 月的 ECMAScript 2017 语言规范中引入的。asyncawait
9赞 nonopolarity 9/10/2019 #35

我认为无论使用什么方法或机制,或者无论你的框架是什么(Angular/React)对你隐藏它,以下原则都成立:

  1. 在程序的流程中(想想代码甚至最低级别的:机器码),数据可能在 2 秒后、3 秒后没有到达,或者可能根本没有到达,所以没有通常使用来返回数据。return

  2. 这是经典的“观察者模式”。(它可以采用“回调”的形式。它是:“嘿,我有兴趣知道数据的成功到达;你能告诉我什么时候吗?因此,您注册一个要接收通知的观察者(或要调用的函数以通知数据成功到达)。您通常还会为此类数据到达失败注册一个观察者。

  3. 当数据成功到达或返回此类数据失败时,注册的观察者(或回调)将与数据一起通知(或与数据一起调用)。如果观察者以回调函数的形式注册,则将被调用。如果观察者以对象的形式注册,则根据接口的不同,它可能会被调用。foofoo(data)foofoo.notify(data)

评论

0赞 Eva Cohen 1/18/2021
你说的“这是经典的观察者模式”是什么意思。callback 不是经典的观察者模式。也许 promise 是经典观察者模式的变体,或者是 PubSub 等的变体。.但绝不是回调本身。至少在我看来是这样。您是否声称所有类型的异步模式都是经典观察者模式的实现?(事件 RXJS Observable 不是,即使 rxjs.subject 是)
0赞 nonopolarity 1/19/2021
我的意思是“结果还没有准备好,当它准备好(或已经改变)时让我知道”所以对于“我如何从异步调用返回响应?”这个问题,它是“使用一种机制在有结果时得到通知”。我想你可以说它是不同的,因为观察者模式可以被多次调用,但这个“回调”或“承诺”只有一次?我专注于“将来准备好时通知我”,而不是“可以调用多少次”。
0赞 Eva Cohen 1/19/2021
好吧,我只争论你的命名,单词的选择。该回调是“经典的观察者模式”,这是一种非常具体的设计模式,可以说是 Promise 的变体。但是回调(尽管它是一个通知工具)并不是经典的观察者模式。在 promise 中,我们可以推送订阅者,这些订阅者将在 promise 结算时收到通知,但回调更像是一个原子单元,而不是一种模式,恕我直言。
1赞 nonopolarity 1/19/2021
好吧,这是关于命名的......我想说的是,这是经典的“准备好后通知我”的模式
30赞 SanjiMika 1/20/2020 #36

在阅读了这里的所有回复和我的经验之后,我想继续详细介绍 JavaScript 中的异步编程。callback, promise and async/await

1)回调:回调的根本原因是运行代码来响应事件(见下面的例子)。我们每次都在 JavaScript 中使用回调。

const body = document.getElementsByTagName('body')[0];
function callback() {
  console.log('Hello');
}
body.addEventListener('click', callback);

但是,如果必须在下面的示例中使用许多嵌套回调,那么对于代码重构来说,这将是可怕的。

asyncCallOne(function callback1() {
  asyncCallTwo(function callback2() {
    asyncCallThree(function callback3() {
        ...
    })
  })
})

2)Promise:一个语法ES6 - Promise解决了回调地狱问题!

const myFirstPromise = new Promise((resolve, reject) => {
  // We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
  // In this example, we use setTimeout(...) to simulate async code.
  // In reality, you will probably be using something like XHR request or an HTML5 API.
  setTimeout(() => {
    resolve("Success!")  // Yay! Everything went well!
  }, 250)
})

myFirstPromise
  .then((res) => {
    return res.json();
  })
  .then((data) => {
    console.log(data);
  })
  .catch((e) => {
    console.log(e);
  });

myFirstPromise 是一个 Promise 实例,表示异步代码的过程。resolve 函数表示 Promise 实例已完成。之后,我们可以在 promise 实例上调用 .then()(.then 的链)和 .catch():

then — Runs a callback you pass to it when the promise has fulfilled.
catch — Runs a callback you pass to it when something went wrong.

3) Async/Await:一种新的语法 ES6 - Await 基本上是 Promise 的语法糖!

Async 函数为我们提供了简洁明了的语法,使我们能够编写更少的代码来实现与 promise 相同的结果。Async/Await 看起来类似于同步代码,同步代码更易于读取和写入。要使用 Async/Await 捕获错误,我们可以使用块 。在这里,你不需要编写 Promise 语法的 .then() 链。try...catch

const getExchangeRate = async () => {
  try {
    const res = await fetch('https://getExchangeRateData');
    const data = await res.json();
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

getExchangeRate();

结论:这完全是异步的三种语法 您应该很好地理解 JavaScript 编程。所以如果可能的话,我 建议将“promise”或“async/await”用于 重构您的异步代码(主要用于 XHR 请求)

评论

0赞 Bharath Ram 8/25/2020
嗨,虽然这个答案的内容是准确的,但它真的没有回答OP的问题(即如何从异步调用中返回一些东西?
13赞 Faiz Mohammed 3/3/2020 #37

无法直接从函数返回 Ajax 响应的结果。原因是 Ajax 调用 ( 或 ) 是异步的,调用封装 Ajax 调用的函数甚至在呈现响应之前也会返回。$.get()$.post()

在这种情况下,唯一的选择是返回一个 promise 对象,该对象在响应到达时解析。

有两种方法可以解决上述问题。 两者都利用了承诺。

下面的代码片段包括一个 JSON URL。两者都可以工作,并且可以直接复制到 JSFiddle 并进行测试。

选项 #1 - 直接从 foo 方法返回 Ajax 调用。
在最新版本的 jQuery 中,Ajax 调用返回一个 promise 对象,可以使用函数解析该对象。在本例中,在代码中,该函数前面是要解析的回调函数。
.then.thenfoo()

   // Declare function foo
   function foo(url)
   {
     return $.get(url);
   }

   // Invoke the foo function, which returns a promise object
   // the 'then' function accepts the call back to the resolve function
   foo('https://jsonplaceholder.typicode.com/todos/1')
     .then(function(response)
     {
       console.log(response);
     })
.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>

选项 #2 - 声明一个 promise 对象并返回它。
在函数中声明一个 promise 对象,将 Ajax 调用封装在该 promise 函数中,并返回 promise 对象。

   function foo1() {
     var promise = new Promise(function(resolve, reject)
     {
       $.ajax({
       url: 'https://jsonplaceholder.typicode.com/todos/1',
       success: function(response) {
           console.log(response);
           resolve(response);
           // return response; // <- I tried that one as well
         }
       });
     });
     return promise;
   }

   foo1()
   .then(function(response)
   {
     console.log('Promise resolved:');
     console.log(response);
   })
.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>

9赞 Philipp Claßen 5/12/2020 #38

最初,回调用于异步操作(例如,在 XMLHttpRequest API 中)。现在,基于承诺的API(如浏览器的Fetch API)已成为默认解决方案,并且所有现代浏览器和Node.js(服务器端)都支持更好的async/await语法。

一个常见的方案 - 从服务器获取 JSON 数据 - 可能如下所示:

async function fetchResource(url) {
  const res = await fetch(url);
  if (!res.ok) {
    throw new Error(res.statusText);
  }
  return res.json();
}

要在另一个函数中使用它:

async function doSomething() {
  try {
    const data = await fetchResource("https://example.test/resource/1");
    // ...
  } catch (e) {
    // Handle error
    ...
  }
}

如果您设计现代 API,强烈建议首选基于 promise 的样式,而不是回调。如果您继承了依赖于回调的 API,则可以将其包装为 promise:

function sleep(timeout) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, timeout);
  });
}

async function fetchAfterTwoSeconds(url) {
  await sleep(2000);
  return fetchResource(url);
}

在历史上完全依赖回调的 Node.js 中,这种技术非常普遍,以至于他们添加了一个名为 util.promisify 的辅助函数。

0赞 Abd Abughazaleh 11/8/2020 #39

异步:false

我通过设置为 false 并重构我的 Ajax 调用来解决它:async

我设置了一个全局函数,该函数调用了三个参数,每次在任何地方调用:sendRequest(type, url, data)

function sendRequest(type, url, data) {
    let returnValue = null;
    $.ajax({
        url: url,
        type: type,
        async: false,
        data: data,
        dataType: 'json',
        success: function (resp) {
            returnValue = resp;
        }
    });
    return returnValue;
}

现在调用函数:

let password = $("#password").val();
        let email = $("#email").val();
        let data = {
            email: email,
            password: password,
        };
        let  resp =  sendRequest('POST', 'http://localhost/signin')}}", data);
        console.log(resp);

代码中的重要说明是:async: false

如果此解决方案不适用于您,请注意,这可能不适用于某些浏览器或jQuery版本。

评论

0赞 Matt Welke 1/1/2021
这在技术上解决了问题,但请注意,不建议这样做,因为它会冻结窗口,直到请求完成。学习如何处理 JS 的异步特性比使用 IO 相关函数的同步版本要好。
5赞 charlietfl 3/27/2021
使用是一种可怕的做法,永远不应该使用。在写这个答案之前几年,浏览器供应商就已经弃用了它。他们甚至在开发工具控制台中警告您,在遇到它时不要使用它async:false
0赞 Michael M. 1/16/2023
这不是 AJAX,这是 SJAX。
48赞 Henke 5/22/2021 #40

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>

哎 呦!
我以为最后打印的行的输出在另一个输出之前打印!
– 而且它不包含结果:它只是打印.1
怎么会这样?
console.log('Finally, the result: ' + result);undefined

有用的见解

我清楚地记得我第一次关于异步调用的 aha (💡) 时刻。 它是:

你实际上不想从回调中获取数据;你想让你的数据需要操作进入回调! 阿拉伯数字

在上面的示例中也是如此。

2. 纯 JavaScript 和回调函数

幸运的是,可以在异步调用完成后编写代码,以处理响应。

一种替代方法是以延续传递样式使用回调函数: 3

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

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

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

注意函数是 。它不返回任何内容。
使用匿名回调函数
() 调用。
这将请求完成后(当可用时)对结果执行所需的操作。
asynchronousFuncvoidasynchronousFuncasynchronousFunc(function (result) {...});responseText

运行上面的代码片段表明,在异步调用之后,我可能不想编写任何代码(例如行)。
为什么?– 因为此类代码将在异步调用提供任何响应数据之前运行。
这样做势必会在将代码输出进行比较时引起混淆。
LAST in the code, but executed FIRST!

3.承诺.then()

该结构于 2015 年 6 月在 ECMA-262 第 6 版中引入。
下面的代码是纯 JavaScript,用 Fetch 替换了老式的 XMLHttpRequest4
.then()

fetch('https://api.chucknorris.io/jokes/random')
  .then((response) => response.json())
  .then((responseBody) => {
    console.log('Using .then() :');
    console.log(responseBody.value + '\n');
  });
.as-console-wrapper { max-height: 100% !important; top: 0; }

4. 承诺async/await

/ 构造于 2017 年 6 月在 ECMA-262 第 8 版中引入。asyncawait

async function awaitAndReceivePromise() {
  const responseBody = (
    await fetch('https://api.quotable.io/quotes/random')
  ).json();
  console.log('Using async/await:');
  const obj = (await responseBody)[0];
  console.log('"' + obj.content + '" – ' + obj.author + '\n');
}

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

如果您决定使用 / 构造,则需要警告。 请注意,在上面的代码片段中,有两个地方需要如何。
如果一开始就忘记了,根本就没有输出。 如果在第二位被遗忘,唯一的输出将是 - 没有其他任何东西被打印出来。
忘记函数的前缀可能是最糟糕的——你会得到一个——而且可能没有关于缺少关键字的提示。
asyncawaitawaitUsing async/await:async"SyntaxError"async


以上所有示例都简明扼要地传达了如何在玩具 API 上使用异步调用。5

引用


1 由提问者表示,因为它们都返回未定义

2 下面详细介绍了异步调用一开始可能会如何令人困惑

3 就像 AJAX 中的 X 一样,这个名字具有误导性——它可以用来检索任何类型的数据,而不仅仅是 XML
如今,Web API 的数据格式无处不在是 JSON,而不是 XML。
XMLHttpRequest

4 Fetch 返回一个 Promise。 我很惊讶地发现 XMLHttpRequestFetch 都不是 ECMAScript 标准的一部分。 JavaScript 可以在此处访问它们的原因是 Web 浏览器提供了它们。
2004 年 6 月成立的 Web 超文本应用技术工作组支持 Fetch 标准和 XMLHttpRequest 标准。\

5 您可能还对如何使用 Promise.all 获取 URL 数组感兴趣?

-6赞 Lionel Yeo 2/26/2023 #41

许多答案没有回答如何获取回调函数中的变量并在以后使用它。

var myvar = '';
var myvar2 = ajax("GET", "/test", "acrive=1").then(function(result) {
    myvar = result.data.myvar # <-- How to populate myvar?
    return myvar;
})

console.log(myvar) 
# returns undefined

console.log(myvar2)
# returns a promise 

为什么

原因是 result.data 是一个承诺。这意味着它是一种不稳定的变化,你不能像变量一样使用它。

那如何使用它呢?

  1. 使用 js 将其转换为 HTML

.then(function(result) {
    myvar = result.data.myvar 
    $('#result').html(myvar) # <-- use the result in the dom
})

这将把它变成一个 dom “常量” 来显示

  1. 使用虚拟 dom,如 Vue.js 变量。vue.js 变量是响应式的,并自动连接到 ajax promise,因此您不必执行任何操作。
myvar = result.data.myvar # <-- How to populate myvar?

var app = new Vue({
   el: '#app',
   data: {
     myvar: ''
   },
   methods: {
     getData() {
         var myvar = '';
         var self = this;
         ajax("GET", "/test", "acrive=1").then(function(result) {
              myvar = result.data.myvar 
              self.myvar = myvar;  #populate this
         })
  }
});
HTML
<div id='app'>
   <div> {{ myvar }} </div>
</div>

评论

1赞 user229044 2/26/2023
这个答案完全没有意义。从异步方法中获取值与 DOM 无关。传递给 AJAX 回调的值不是 promise。向 DOM 添加值并不能神奇地解析 promise,如果你将 promise 传递给 DOM,那么你得到的只是附加到 DOM 中的东西。 Vue 与这里的任何事情都无关。$("#result").html(x)[object Object]
0赞 VLAZ 2/26/2023
@user229044同意这毫无意义。解释已关闭。但是,它确实包含一个有效的解决方案,它并不是完全不透明的。所需要的只是将使用的代码放在填充的 promise 处理程序中。所有关于承诺的讨论实际上都是错误的,因为这不是一个承诺。Vue 的解决方案并不依赖于用 promise 填充某些东西——它只是一个被再次使用的普通值。所以解释是完全错误的。此外,即使是解决方案的工作部分也在重复现有的答案。myvarmyvarmyvar
0赞 Lionel Yeo 2/27/2023
1) 注释 1 将代码放在回调处理程序之外,此后我更新了答案,因此更清晰。2) 评论 2.你错了。尝试在回调处理程序之外使用 myvar,它将是未定义的。这就是为什么我们需要讨论应许。 不是一个普通的值。另外,是的,我确实使用了解决方案的其他部分,但是使用响应式 vue.js 可以处理价值。myvar
0赞 VLAZ 2/27/2023
"尝试在回调处理程序之外使用 myvar,它将是未定义的。是的。因此,为什么我说与此完全相反。再读一遍:“只需要将使用 myvar 的代码放在填充 myvar 的 promise 处理程序中。“myvar 不是一个普通的值”,它是。它只是异步填充的。然而,这不是一个承诺。你可以说出来,因为*它和你说的一模一样。如果这是一个承诺,那将是......一个承诺,而不是一个普通的价值。儗undefinedconsole.log(Promise.resolve(undefined))
-2赞 Zia 6/10/2023 #42

我遵循这两种方式和Promises async/await

承诺:

function makeAsyncCall() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const response = 'Async response';
      resolve(response);
    }, 2000);
  });
}

makeAsyncCall()
  .then(response => {
    console.log(response); 
  })
  .catch(error => {
    console.error(error); 
  });

和 async/await:

async function makeAsyncCall() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const response = 'Async response';
      resolve(response);
    }, 2000);
  });
}

async function handleAsyncCall() {
  try {
    const response = await makeAsyncCall();
    console.log(response); 
  } catch (error) {
    console.error(error); 
  }
}

handleAsyncCall();