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

2024-11-02 20:49:00
admin
原创
35
摘要:问题描述:foo如何从发出异步请求的函数返回响应/结果?我试图从回调中返回值,以及将结果分配给函数内部的局部变量并返回该变量,但这些方法实际上都没有返回响应 - 它们都返回undefined变量的初始值result。接受回调的异步函数示例(使用 jQuery 的ajax函数):function 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`
}

解决方案 1:

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

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

问题

Ajax中的A代表异步。这意味着发送请求(或者说接收响应)脱离了正常执行流程。在您的示例中,立即返回,下一个语句在您作为回调传递的函数被调用之前执行。$.ajax`return result;`success

这是一个类比,希望能够更清楚地说明同步流和异步流之间的区别:

同步

想象一下,你给朋友打电话,请他帮你查点东西。虽然这可能需要一段时间,但你还是在电话里等待,直到你的朋友给你所需的答案。

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

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

var item = findItem();

// Do something with item
doSomethingElse();

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

异步

你再次打电话给朋友,理由是相同的。但这次你告诉他你很着急,让他用手机给你回电话。你挂断电话,离开家,做你计划做的事。一旦你的朋友给你回电话,你就得处理他给你的信息。

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

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

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


解决方案

拥抱 JavaScript 的异步特性!虽然某些异步操作提供了同步对应功能(“Ajax” 也是如此),但一般不鼓励使用它们,尤其是在浏览器环境中。

你问这为什么不好?

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

所有这些都会导致非常糟糕的用户体验。用户无法判断一切是否正常。此外,对于网速较慢的用户来说,影响会更糟。

下面我们将介绍三种相互叠加的不同解决方案:

  • Promisesasync/await(ES2017+,如果你使用转换器或再生器,可以在旧版浏览器中使用)

  • 回调(在节点中很流行)

  • Promisesthen()(ES2015+,如果你使用其中一个 Promises 库,则可在旧版浏览器中使用)

这三种版本均可在当前浏览器和 Node 7+ 中使用。


ES2017+:使用 Promiseasync/await

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

async/await建立在承诺之上:async函数总是返回一个承诺。await“解开”一个承诺,并产生承诺解决的值,或者如果承诺被拒绝则引发错误。

重要提示:您只能在函数await内部async或JavaScript 模块中使用。模块外部不支持顶层,因此如果不使用模块await,您可能必须创建异步 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 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);
})();

当前浏览器和Node版本均支持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'
});

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

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

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

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

以下是使用承诺的一个例子:

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

运行代码片段Hide results展开片段

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

function ajax(url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      resolve(this.responseText);
    };
    xhr.onerror = reject;
    xhr.open('GET', url);
    xhr.send();
  });
}

ajax("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; }

运行代码片段Hide results展开片段

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

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

附注:jQuery 的延迟对象

延迟对象是 jQuery 对承诺的自定义实现(在 Promise API 标准化之前)。它们的行为几乎与承诺相似,但暴露的 API 略有不同。

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

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

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

附注:Promise 陷阱

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

function checkPassword() {
    return $.ajax({
        url: '/password',
        data: {
            username: $('#username').val(),
            password: $('#password').val()
        },
        type: 'POST',
        dataType: 'json'
    });
}

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

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

但修复起来很简单:

checkPassword()
.done(function(r) {
    if (r) {
        // Tell the user they're logged in
    } else {
        // Tell the user their password was bad
    }
})
.fail(function(x) {
    // Tell the user something bad happened
});

不推荐:同步“Ajax”调用

正如我所提到的,一些异步操作有同步对应操作。我不提倡使用它们,但为了完整性,下面是执行同步调用的方法:

没有 jQuery

如果直接使用XMLHttpRequest对象,请将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 本质上始终是异步的(这也是不考虑此选项的另一个原因)。

解决方案 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 for AJAX 的人们提供另一种选择。

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


你面临的情况

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

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

可能的解决方案

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

  1. 使 AJAX 调用同步(我们称之为 SJAX)。

  2. 重构您的代码以便能够正确地与回调一起工作。

  3. 同步 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);
}
  1. 重构代码


让您的函数接受回调。在示例代码中,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 入门指南。

解决方案 3:

XMLHttpRequest 2(首先,阅读Benjamin Gruenbaum和Felix Kling的答案)

如果你不使用 jQuery 并且想要一个可以在现代浏览器和移动浏览器中运行的简短 XMLHttpRequest 2,我建议按以下方式使用它:

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

如你看到的:

  1. 它比列出的所有其他函数都短。

  2. 直接设置回调(因此没有额外的不必要的闭包)。

  3. 它使用新的 onload(因此您不必检查 readystate && status)

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

使用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”作为表单数据,它也不会引发错误。

在第一种情况下,错误出现在displayAjax()之下this.statusTextMethod not Allowed

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

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

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

只有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 做很多事情:

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

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

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

解决方案 4:

如果您正在使用承诺,那么这个答案适合您。

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

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

function foo() {
    var data;
    // Or $.get(...).then, or request(...).then, or query(...).then
    fetch("/echo/json").then(function(response){
        data = response.json();
    });
    return data;
}

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

Felix Kling为使用 jQuery 和 Ajax 回调的人们写了一个很棒的答案。我有一个针对原生 XHR 的答案。这个答案适用于在前端或后端通用使用承诺。


核心问题

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

每当您调用返回承诺的方法时,then处理程序总是异步执行 - 也就是说,在处理程序中不存在的代码之后.then执行。

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

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

    function getFive(){
        var data;
        setTimeout(function(){ // Set a timer for one second in the future
           data = 5; // After a second, do this
        }, 1000);
        return data;
    }
    document.body.innerHTML = getFive(); // `undefined` here and not 5

运行代码片段Hide results展开片段

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

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

解决此问题的一个可能方法是重新编写代码,告诉程序计算完成后该做什么。Promises 本质上是时间敏感的,因此可以积极地实现这一点。

快速回顾一下承诺

Promise 是随时间变化的值。Promise 具有状态。它们以待定状态开始,没有值,并且可以稳定为:

  • done表示计算已成功完成。

  • 拒绝意味着计算失败。

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

当我们调用.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;
});

运行代码片段Hide results展开片段

基本上,我们不会返回一个由于并发模型而无法执行的值,而是返回一个可以解开的值的包装器。这就像一个可以用打开的盒子。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`
})

所以这也行得通。我们已经知道我们不能从已经异步的调用中返回值,但我们可以使用承诺并将它们链接起来以执行处理。我们现在知道如何从异步调用中返回响应。

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.coroutine在这里使用 Bluebird,但还有其他包装器,如coQ.async

var foo = coroutine(function*(){
    var data = yield fetch("/echo/json"); // Notice the yield
    // The code here only executes _after_ the request is done
    return data.json(); // 'data' is defined
});

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

var main = coroutine(function*(){
   var bar = yield foo(); // Wait our earlier coroutine. It returns a promise
   // The server call is done here, and the code below executes when done
   var baz = yield fetch("/api/users/" + bar.userid); // Depends on foo's result
   console.log(baz); // Runs after both requests are done
});
main();

ES2016 (ES7)

在 ES7 中,这进一步标准化了。目前有几项提案,但您可以承诺所有提案await。这只是通过添加asyncawait关键字对上述 ES6 提案进行“修饰”(更好的语法)。制作上面的例子:

async function foo(){
    var data = await fetch("/echo/json"); // Notice the await
    // code here only executes _after_ the request is done
    return data.json(); // 'data' is defined
}

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

解决方案 5:

您使用 Ajax 的方式不正确。其想法是不要让它返回任何内容,而是将数据交给回调函数来处理数据。

那是:

function handleData( responseData ) {

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

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

在提交处理程序中返回任何内容都不会产生任何效果。您必须转而交出数据,或者直接在成功函数中对其进行处理。

解决方案 6:

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

在此处输入图片描述

解决方案 7:

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

解决方案 8:

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

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

例子:

显示代码片段

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

Run code snippetHide resultsExpand snippet

不起作用的原因是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; }

Run code snippetHide resultsExpand snippet

(我们可以废除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; }

Run code snippetHide resultsExpand snippet

或者这里有一个返回 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; }

Run code snippetHide resultsExpand snippet

(或者,您可以创建一个doSomethingAsync返回承诺的包装器,然后执行以下操作......)

如果doSomethingAsync给你一个承诺,你可以使用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; }

Run code snippetHide resultsExpand snippet

请注意,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; }

Run code snippetHide resultsExpand snippet

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

如果给你一个 Promise,如果你可以使用 ES2017+ 语法(也许使用像BabeldoSomethingAsync这样的转换器),你可以使用带有和 的函数:async`for-of`await

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

Run code snippetHide resultsExpand snippet

如果你还不能使用 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; }

Run code snippetHide resultsExpand snippet

...使用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; }

Run code snippetHide resultsExpand snippet

解决方案 9:

角度 1

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

这里说,

Promises 可用于解除异步函数的嵌套,并允许将多个函数链接在一起。

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

在下面提到的文档中找到的一个例子。

  promiseB = promiseA.then(
    function onSuccess(result) {
      return result + 1;
    }
    ,function onError(err) {
      // Handle error
    }
  );

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

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,如果你想使用它,你可能需要插件。

此外,这里是承诺规范。

解决方案 10:

看一下这个例子:

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

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

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

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

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

这是 plnkr:

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

ES6 方式(异步 - 等待)

(function(){
  async function getJoke(){
    let response = await fetch('http://api.icndb.com/jokes/random');
    let data = await response.json();
    return data.value;
  }

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

解决方案 11:

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

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

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

您可以使用回调、承诺和最近的可观察对象来处理它。例如,在承诺中,我们有一些函数,如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
});

欲了解更多信息,请研究承诺和可观察对象,它们是执行异步操作的较新方法。

解决方案 12:

这是我们在努力破解 JavaScript 的“秘密”时面临的一个非常常见的问题。今天让我来揭开这个谜团。

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

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

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

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

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

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

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

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

函数foo()执行后不返回任何内容(因此返回值为undefined),但它会启动一个计时器,该计时器会在 1 秒后执行一个函数并返回“哇哦”​​。但正如您所见,分配给 bar 的值是 foo() 立即返回的内容,它什么都没有,即只是undefined

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

让我们要求函数提供承诺。承诺的真正含义是:它意味着该函数保证您将来提供它获得的任何输出。那么让我们看看它如何解决上面的小问题:

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值(您打算返回的值)使用承诺。因此,简而言之,在异步函数中,您解析值而不是返回。

更新(使用 async/await 的承诺)

除了使用then/catchPromise 之外,还有一种方法。其思想是识别异步函数,然后等待 Promise解析,然后再转到下一行代码。它仍然只是底层promises,但采用了不同的语法方法。为了更清楚起见,您可以在下面找到比较:

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

解决方案 13:

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

以下是相同示例:

var async = require("async");

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

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

result在异步操作期间使用对象来存储值。这样即使在异步作业之后也可以获得结果。

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

解决方案 14:

虽然承诺和回调在很多情况下都能很好地工作,但表达如下内容却很麻烦:

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
}

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

解决方案 15:

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

  • 处理异步 HTTP 调用;

  • 等待每个 API 调用的响应;

  • 使用Promise模式;

  • 使用Promise.all模式连接多个 HTTP 调用;

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

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

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

对于每个项目,新的 Promise 将触发一个块 -ExecutionBlock解析结果,根据结果数组安排一组新的承诺,即 Spotifyuser对象的列表,并在异步中执行新的 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" />

运行代码片段Hide results展开片段

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

解决方案 16:

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

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

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

解决方案 17:

JavaScript 是单线程的。

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

  1. 事件循环

  2. Web API

  3. 事件队列

事件循环永远运行,即某种无限循环。事件队列是所有函数在发生某个事件(例如:单击)时被推送到的位置。

它会逐个从队列中取出并放入事件循环中,事件循环会在第一个函数执行完成后执行当前函数并为下一个函数做好准备。这意味着一个函数的执行要等到队列中它之前的函数在事件循环中执行完毕后才会开始。

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

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

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

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

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

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

  • 此处其中一个答案中的图像正确解释了回调的使用...*

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

打回来

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

解决方案 18:

2017 年答案:你现在可以在每一个当前浏览器和Node.js中做你想做的事

这很简单:

  • 返回承诺

  • 使用'await',它将告诉 JavaScript 等待承诺被解析为一个值

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用