6298

我有一个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`
}
4

42 回答 42

6302

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

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

问题

Ajax中的A代表异步。这意味着发送请求(或者更确切地说是接收响应)从正常的执行流程中取出。在您的示例中,立即返回,并且在您作为回调传递的函数甚至被调用之前执行下一条语句。$.ajaxreturn 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 调用之后的语句。为了最终获得响应,您提供了一个在收到响应后调用的函数,即回调(注意什么?回调?)。在调用回调之前执行该调用之后的任何语句。


解决方案

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

你问为什么不好?

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

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

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

  • Promises withasync/await (ES2017+, 如果你使用转译器或再生器,在旧版浏览器中可用)
  • 回调(在节点中流行)
  • Promises withthen() (ES2015+, 如果你使用许多 Promise 库之一,则在旧浏览器中可用)

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


ES2017+:承诺与async/await

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

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

重要提示:您只能在函数await内部使用asyncawait目前,尚不支持顶级,因此您可能必须创建一个异步 IIFE(立即调用函数表达式)来启动async上下文。

您可以阅读有关MDN 的async更多信息。await

这是一个详细说明上述延迟函数的示例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);
})();

当前浏览器节点版本支持async/await. 您还可以通过在regenerator(或使用 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. 即,一旦 Ajax 请求成功,$.ajax将调用callback并将响应传递给回调(可以用 引用result,因为这是我们定义回调的方式)。

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

function foo(callback) {
    $.ajax({
        // ...
        success: function(response) {
            // For example, filter the response
            callback(filtered_response);
        }
    });
}

使用回调编写代码比看起来更容易。毕竟,浏览器中的 JavaScript 是高度事件驱动的(DOM 事件)。接收 Ajax 响应只不过是一个事件。当您必须使用第三方代码时可能会出现困难,但大多数问题都可以通过考虑应用程序流程来解决。


ES2015+:使用then() 进行承诺

Promise API是 ECMAScript 6 (ES2015) 的一个新特性,但它已经有很好的浏览器支持。还有许多库实现了标准 Promises API 并提供了额外的方法来简化异步函数的使用和组合(例如bluebird)。

Promise 是未来值的容器。当 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; }

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

有关 Promises 的更多信息: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
});

旁注:承诺陷阱

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

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 对象responseText的属性:

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

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

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

于 2013-01-08T17:06:14.013 回答
1167

如果您没有在代码中使用 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 的人提供一个替代方案。

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


你所面临的

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

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

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

这是一个简单的类比:

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

(小提琴)

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

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

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

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

这称为CPS。基本上,我们传递getFive一个动作完成时执行,我们告诉我们的代码如何在事件完成时做出反应(比如我们的 AJAX 调用,或者在这种情况下是超时)。

用法是:

getFive(onComplete);

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

可能的解决方案

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

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

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

至于同步AJAX,千万别搞!Felix 的回答引发了一些令人信服的论点,说明为什么这是一个坏主意。总而言之,它会冻结用户的浏览器,直到服务器返回响应并造成非常糟糕的用户体验。这是从 MDN 摘录的另一个简短摘要,说明了原因:

XMLHttpRequest 支持同步和异步通信。然而,一般来说,出于性能原因,异步请求应优先于同步请求。

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

如果你必须这样做,你可以传递一个标志。方法如下

var request = new XMLHttpRequest();
request.open('GET', 'yourURL', false);  // `false` makes the request synchronous
request.send(null);

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

2.重构代码

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

所以:

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

变成:

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

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

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

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

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

function foo(callback) {
    var httpRequest = new XMLHttpRequest();
    httpRequest.onload = function(){ // When the request is loaded
       callback(httpRequest.responseText);// We're calling our method
    };
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
}

(小提琴)

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

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

于 2013-05-29T23:30:56.593 回答
428

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

如果你不使用 jQuery 并且想要一个在现代浏览器和移动浏览器中工作的漂亮的简短 XMLHttpRequest 2,我建议以这种方式使用它:

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

如你看到的:

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

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

最简单的:

this.response

或者如果由于某种原因你bind()回调一个类:

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 的标头时才有用。

使用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 传递,它也不会引发错误。

在第一种情况下,错误在as的displayAjax()下方。this.statusTextMethod not Allowed

在第二种情况下,它可以正常工作。如果您传递了正确的发布数据,您必须在服务器端进行检查。

不允许跨域自动抛出错误。

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

只有this.typewhich 设置为error

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

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

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


同步读取文件

不要那样做。

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

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

现在你可以做

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

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

另一点是...如果您使用 API 或只是您自己的列表文件或任何您总是为每个请求使用不同功能的东西...

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


以上功能为基本使用。

如果要扩展功能...

是的你可以。

我使用了很多 API,我集成到每个 HTML 页面的第一个函数是这个答案中的第一个 Ajax 函数,只有 GET ......

但是你可以用 XMLHttpRequest 2 做很多事情:

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

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

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

于 2013-08-19T08:06:04.867 回答
350

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

这意味着 AngularJS、jQuery(带延迟)、本机XHR的替换(获取)、Ember.jsBackbone.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 为使用带有 Ajax 回调的 jQuery 的人们编写了一个很好的答案。我有一个原生 XHR 的答案。这个答案适用于前端或后端的 Promise 的通用用法。


核心问题

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

每当您调用返回承诺的方法时,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

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

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

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

快速回顾承诺

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

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

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

当我们调用.then一个 Promise 并从中返回一些东西时——我们得到一个处理后的值的 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;
});

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

应用这个

这与您的原始 API 调用相同,您可以:

function foo() {
    // RETURN the promise
    return fetch("/echo/json").then(function(response){
        return response.json(); // Process it inside the `then`
    });
}

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

所以这同样有效。我们已经知道我们不能从已经异步的调用中返回值,但是我们可以使用 Promise 并链接它们来执行处理。我们现在知道如何从异步调用返回响应。

ES2015 (ES6)

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

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

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

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

这有点棘手,但非常强大的技巧让我们以同步的方式编写异步代码。有几个“跑步者”可以为你做这件事。写一个是短短的几行代码,但这超出了这个答案的范围。我将在Promise.coroutine这里使用 Bluebird,但还有其他包装器,例如coor Q.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做出承诺。async通过添加andawait关键字,这只是上面 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
}

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

于 2015-05-12T02:22:57.533 回答
276

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

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

于 2014-05-23T02:05:01.057 回答
265

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

在此处输入图像描述

于 2016-08-11T14:17:36.010 回答
256

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

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);
});
于 2014-02-18T18:58:37.407 回答
177

角 1

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

这里说,

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中使用observables

 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 规范

于 2014-08-26T08:11:36.220 回答
176

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

// 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在通话未完成时更改...)

请注意我们如何使用indexfromforEach将结果保存在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; }

或者这是一个返回 a 的版本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函数和: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”模式的变体(这比通常的 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; }

于 2017-05-03T16:59:06.400 回答
122

看看这个例子:

var app = angular.module('plunker', []);

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

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

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

如您所见getJoke,它返回了一个已解决的承诺(返回时已解决res.data.value)。所以你等到$http.get请求完成,然后执行console.log(res.joke)(作为正常的异步流程)。

这是 plnkr:

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);
  });
})();
于 2016-06-02T08:31:13.677 回答
121

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

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

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

你可以使用回调、promise 和最近 observable 来为你处理它。例如,在 Promise 中,我们有一些类似success()orthen()的函数,当你的数据准备好时会执行。与可观察对象上的回调或订阅函数相同。

例如,在您使用 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,它们是执行此异步操作的新方法。

于 2017-05-24T09:38:10.597 回答
118

这是我们在与 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 秒后执行一个函数以返回 'woohoo'。但是正如你所看到的,分配给 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/catchpromise 之外,还有另一种方法。这个想法是识别一个异步函数,然后等待 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);
     }
  }
于 2017-10-31T20:12:45.767 回答
109

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

这是一个相同的例子:

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在异步操作期间使用对象来存储值。即使在异步作业之后,这也允许结果可用。

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

于 2015-09-02T12:54:37.763 回答
96

虽然 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
}

您可以在此处签出该项目。

于 2016-01-25T17:43:56.100 回答
93

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

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

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

语境。该示例查询Spotify Web API端点以搜索playlist给定查询字符串集的对象:

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

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

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

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

-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" />

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

于 2016-04-12T22:55:39.063 回答
92

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

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

$.ajax({
    url: "...",
    success: callback
});
于 2016-04-22T14:47:08.960 回答
87

JavaScript 是单线程的。

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

  1. 事件循环

  2. 网络 API

  3. 事件队列

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

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

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

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

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

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

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

因此,解决方案callbackpromise

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

打回来

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

于 2018-02-03T06:06:53.637 回答
85

2017 年回答:您现在可以在每个当前浏览器和Node.js中执行您想要的操作

这很简单:

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

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

(async function(){

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

})()

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

于 2017-06-02T09:51:10.950 回答
71

您可以使用这个自定义库(使用 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)
});
于 2016-05-26T13:26:40.817 回答
71

另一种解决方案是通过顺序执行器 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 包装器中(如果它有 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;

步骤 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 将逐步评估所有运算符和表达式,如果某些慢速函数的结果未准备好,则暂停执行。

更多示例在这里

于 2017-05-27T02:47:04.280 回答
44

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。

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

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
于 2018-02-17T15:26:28.153 回答
41

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

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

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

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

因为 JavaScript 无法知道在执行之前需要等待order_milk完成put_in_coffee。换句话说,它不知道这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)

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

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);
}
于 2016-01-23T03:28:39.267 回答
40

简短回答:您的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.

于 2015-09-23T22:52:03.907 回答
40

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

  1. 浏览器承诺对象
  2. Q - JavaScript 的 promise 库
  3. A+ Promises.js
  4. jQuery 延迟
  5. XMLHttpRequest API
  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();

于 2016-08-13T09:36:41.877 回答
39

使用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();
于 2017-04-24T08:09:03.637 回答
34

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); 我认为最后打印的行的 输出实际上是在另一个输出之前打印的!– 它不包含结果:它只是打印undefined. 1 怎么来的?

有用的见解

我清楚地记得我的第一个啊哈!关于如何理解异步调用的时刻。
正是这条评论说:
您实际上不想回调中获取数据;
您想将需要数据的操作放入回调中!
2
这在上面的例子中很明显。但是,在处理响应完成后的异步调用之后
仍然可以编写代码吗?

2. 纯 JavaScript 和回调函数

答案是肯定的!- 有可能的。
一种替代方法是在延续传递样式中 使用回调函数: 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; }

注意函数asynchronousCall是怎样的void。它什么也不返回。相反,通过asynchronousCall使用匿名回调函数 ( asynchronousCall(function (result) {...) 调用,此函数对结果执行所需的操作,但仅请求完成后 -responseText可用时。

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

.then()3. 用– 或async/承诺await

.then()构造于2015 年 6 月在 ECMA-262 第 6 版中引入,而async/await构造于2017 年 6 月在 ECMA-262 第 8 版中引入。
下面的代码仍然是纯 JavaScript,将老式的 XMLHttpRequest替换为Fetch4

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 构造,则需要警告。请注意上面的代码片段中两个地方await需要如何。如果一开始就忘记了,就没有输出。如果第二次忘记了,则唯一的输出将是空对象 (或或)。 忘记函数的前缀可能是最糟糕的——输出将是——没有提到缺少的关键字。{}[object Object][object Promise]
async"SyntaxError: missing ) in parenthetical" async

4. Promise.all – URL 数组5

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

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

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 结果数组(标题)与之合并:

namesonly = ['two', 'three']

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

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

我已将名称更改titleloremipsum.

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中可视化混搭

MusicBrainz API 包含有关艺术家和乐队的信息。一个例子——英国摇滚乐队酷玩
乐队的请求是: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 片段显示 Coldplay 的第一张专辑是Parachutes。它还给出了一个id,在这种情况下1dc4c347-a1db-32aa-b14f-bc9cc507b843,它是专辑的唯一标识符。

此标识符可用于在封面艺术档案 API中进行查找:http :
//coverartarchive.org/release-group/1dc4c347-a1db-32aa-b14f-bc9cc507b8437

对于每张专辑,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 可视化乐队的所有专辑标题和封面。如何编写代码来实现这一点已经在 对如何在 Postman 中可视化 API mashup的问题 的回答中进行了相当详细的描述。– 因此,我将避免在这里进行冗长的讨论,而仅提供代码和结果的屏幕截图:

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 中的结果和文档


如何下载和运行 Postman Collection

运行 Postman Collection 应该很简单。
假设您使用的是桌面版 Postman,请执行以下操作:

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

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

  3. 收藏 > MusicBands> DummyRequest>发送8

  4. 在 Postman 响应正文中,单击Visualize

  5. 您现在应该可以滚动 15 个专辑,如上面的屏幕截图所示。

参考


1原发帖人表述为:都归来 undefined
2如果您认为异步调用令人困惑,请考虑查看一些有关异步调用的问题和答案,看看是否有帮助。
3这个名称与 AJAX中的XXMLHttpRequest一样具有误导性——如今,Web API 的数据格式普遍是 JSON,而不是 XML。4 Fetch 返回一个Promise。我惊讶地发现XMLHttpRequestFetch都不是ECMAScript的一部分
标准。JavaScript 可以在这里访问它们的原因是 Web 浏览器提供了它们。 2004 年 6 月成立的 Web 超文本应用程序技术工作组 (WHATWG)都支持 Fetch 标准XMLHttpRequest 标准。 5本节借鉴了 How can I fetch a array of URLs with Promise.all?. 6本节主要依赖于 如何在 Postman 中可视化 API mashup?. 7此 URL 会自动重定向到: https ://ia800503.us.archive.org/29/items/mbid-435fc965-9121-461e-b8da-d9b505c9dc9b/index.json 。8如果出现错误,



运行脚本时出现问题,请再次点击发送

于 2021-05-22T10:24:15.720 回答
31

使用承诺

这个问题最完美的答案是使用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:未定义承诺

所以我决定在JavaScript 编译器下面实现我自己的 ES3 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;
    }();
}
于 2018-12-07T14:10:23.677 回答
30

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

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

function foo() {
    var result;

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

    return result;
}

function myCallback(response) {
    // Does something.
}
于 2017-07-05T20:28:42.720 回答
30

问题是:

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

可以解释为:

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

解决方案是避免回调,并结合使用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")
于 2018-01-13T19:13:20.800 回答
28

不要向你扔代码,有两个概念是理解 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使用回调(例如传递给的函数.then()作为以更线性的方式推理这种异步行为的一种方式。承诺是一种说“我承诺在某个时候返回某些东西”的方式,而回调是我们处理最终返回的值的方式。jQuery 使用称为deffered.done deffered.failand的特定回调deffered.always(其中包括)。你可以在这里看到它们

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

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

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

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

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

于 2018-05-04T15:56:07.643 回答
27

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

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 捕获错误,我们可以使用 block try...catch。在这里,您不需要编写 Promise 语法的 .then() 链。

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 请求)

于 2020-01-19T22:23:07.037 回答
26

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

于 2018-01-24T06:18:55.310 回答
24

这是一个有效的示例:

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>

于 2018-07-12T21:48:15.560 回答
19

等待

请求以异步方式工作,因此您无法像在典型代码中那样同步读取数据。但是,使用async/await您可以创建看起来接近/类似于通常的同步/顺序样式的异步代码。处理响应数据的代码需要由async函数包装(load在下面的代码片段中),并且在其中您需要在await之前添加关键字foo()(也使用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();

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

于 2019-06-11T07:56:10.160 回答
15

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

这里有很多详细的信息性答案,我不会重复任何一个。JavaScript 编程的关键是首先拥有正确的整体执行思维模型

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

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

但是,在您对实际的问题领域感到满意之前,您不应该使用任何战术工具来解决问题。绘制这些依赖关系的地图,以了解何时需要运行什么。尝试对所有这些回调采取一种特别的方法是不会为您提供很好的服务。

于 2017-10-25T03:22:21.970 回答
11

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

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

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

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

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

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

于 2020-03-03T06:25:29.670 回答
7

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

  1. 在程序的流程中(想想代码甚至是最低层:机器码),数据可能2秒后回不来,3秒后,或者根本就没有,所以没有平时return使用的顺序返回数据。

  2. 这是经典的“观察者模式”。(它可以是“回调”的形式。)它是:“嘿,我很想知道数据是否成功到达;当它成功时你能告诉我吗?” 因此,您注册了一个观察者以得到通知(或调用一个函数来通知数据成功到达。)您通常还注册一个观察者来通知此类数据的到达失败。

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

于 2019-09-10T07:25:53.897 回答
7

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

一个常见的场景 - 从服务器获取 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.

于 2020-05-12T09:56:33.080 回答
2

异步:假

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

我设置了一个全局函数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版本。

于 2020-11-08T10:17:11.017 回答
2

async/awaitBabel之类的编译器一起使用以使其在旧浏览器中工作。你还必须从 npm 安装这个 Babel 预设和 polyfill npm 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)
}
于 2019-04-16T16:13:16.973 回答
-4

将 Node.js 上的XHR转换为 async-await 的简单代码示例

var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
var xhttp = new XMLHttpRequest();

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

// We need to wrap await in Async function so and anonymous IIFE here
(async _ => {
  try {
    let result = await xhrWrapWithPromise();
    console.log(result);
  } catch (error) {
    console.log(error);
  }
})();
于 2018-12-02T12:41:39.607 回答
-6

由于await总是返回一个 Promise,只需做一个额外的await(在async函数内部)来提取值:

test(); // This alerts "hello"

// This is the outer function that wants to get the string result of inner()
async function test() {
  var str=await await inner();
  alert(str);
} // test

// This ia an inner function that can do arbitrary async operations
async function inner() {
  return Promise.resolve('hello');
}

于 2020-10-31T17:17:55.947 回答