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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14220321/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 16:04:37  来源:igfitidea点击:

How do I return the response from an asynchronous call?

javascriptjqueryajaxasynchronous

提问by Felix Kling

I have a function foowhich makes an Ajax request. How can I return the response from foo?

我有一个foo发出 Ajax 请求的函数。我怎样才能从 返回响应foo

I tried returning the value from the successcallback, as well as assigning the response to a local variable inside the function and returning that one, but none of those ways actually return the response.

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

function foo() {
    var result;

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

    return result;
}

var result = foo(); // It always ends up being `undefined`.

回答by Felix Kling

→ For a more general explanation of async behaviour with different examples, please seeWhy is my variable unaltered after I modify it inside of a function? - Asynchronous code reference

→ If you already understand the problem, skip to the possible solutions below.

→ 如需使用不同示例对异步行为进行更一般的解释,请参阅为什么在函数内部修改我的变量后未更改?- 异步代码参考

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

The problem

问题

The Ain Ajaxstands for asynchronous. That means sending the request (or rather receiving the response) is taken out of the normal execution flow. In your example, $.ajaxreturns immediately and the next statement, return result;, is executed before the function you passed as successcallback was even called.

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

Here is an analogy which hopefully makes the difference between synchronous and asynchronous flow clearer:

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

Synchronous

同步

Imagine you make a phone call to a friend and ask him to look something up for you. Although it might take a while, you wait on the phone and stare into space, until your friend gives you the answer that you needed.

想象一下,你给朋友打电话,让他​​帮你查点东西。虽然这可能需要一段时间,但您会等待电话并凝视太空,直到您的朋友给您所需的答案。

The same is happening when you make a function call containing "normal" code:

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

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

var item = findItem();

// Do something with item
doSomethingElse();

Even though findItemmight take a long time to execute, any code coming after var item = findItem();has to waituntil the function returns the result.

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

Asynchronous

异步

You call your friend again for the same reason. But this time you tell him that you are in a hurry and he should call you backon your mobile phone. You hang up, leave the house and do whatever you planned to do. Once your friend calls you back, you are dealing with the information he gave to you.

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

That's exactly what's happening when you do an Ajax request.

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

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

Instead of waiting for the response, the execution continues immediately and the statement after the Ajax call is executed. To get the response eventually, you provide a function to be called once the response was received, a callback(notice something? call back?). Any statement coming after that call is executed before the callback is called.

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



Solution(s)

解决方案

Embrace the asynchronous nature of JavaScript!While certain asynchronous operations provide synchronous counterparts (so does "Ajax"), it's generally discouraged to use them, especially in a browser context.

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

Why is it bad do you ask?

你问为什么不好?

JavaScript runs in the UI thread of the browser and any long-running process will lock the UI, making it unresponsive. Additionally, there is an upper limit on the execution time for JavaScript and the browser will ask the user whether to continue the execution or not.

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

All of this is really bad user experience. The user won't be able to tell whether everything is working fine or not. Furthermore, the effect will be worse for users with a slow connection.

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

In the following we will look at three different solutions that are all building on top of each other:

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

  • Promises with async/await(ES2017+, available in older browsers if you use a transpiler or regenerator)
  • Callbacks(popular in node)
  • Promises with then()(ES2015+, available in older browsers if you use one of the many promise libraries)
  • Promises withasync/await(ES2017+,如果您使用转译器或再生器,则在旧浏览器中可用)
  • 回调(在节点中流行)
  • Promises withthen()(ES2015+,如果您使用众多 Promise 库之一,则可在旧浏览器中使用)

All three are available in current browsers, and node 7+.

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



ES2017+: Promises with async/await

ES2017+:承诺 async/await

The ECMAScript version released in 2017 introduced syntax-level supportfor asynchronous functions. With the help of asyncand await, you can write asynchronous in a "synchronous style". The code is still asynchronous, but it's easier to read/understand.

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

async/awaitbuilds on top of promises: an asyncfunction always returns a promise. await"unwraps" a promise and either result in the value the promise was resolved with or throws an error if the promise was rejected.

async/await建立在承诺之上:一个async函数总是返回一个承诺。await“解开”一个promise 并且要么导致promise 被解决的值,要么在promise 被拒绝时抛出一个错误。

Important:You can only use awaitinside an asyncfunction. Right now, top-level awaitisn't yet supported, so you might have to make an async IIFE (Immediately Invoked Function Expression) to start an asynccontext.

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

You can read more about asyncand awaiton MDN.

你可以在 MDN 上阅读更多关于asyncawait

Here is an example that builds on top of delay above:

这是一个建立在上述延迟之上的示例:

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

Current browserand nodeversions support async/await. You can also support older environments by transforming your code to ES5 with the help of regenerator(or tools that use regenerator, such as Babel).

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



Let functions accept callbacks

让函数接受回调

A callback is simply a function passed to another function. That other function can call the function passed whenever it is ready. In the context of an asynchronous process, the callback will be called whenever the asynchronous process is done. Usually, the result is passed to the callback.

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

In the example of the question, you can make fooaccept a callback and use it as successcallback. So this

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

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

becomes

变成

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

Here we defined the function "inline" but you can pass any function reference:

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

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

foo(myCallback);

fooitself is defined as follows:

foo本身定义如下:

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

callbackwill refer to the function we pass to foowhen we call it and we simply pass it on to success. I.e. once the Ajax request is successful, $.ajaxwill call callbackand pass the response to the callback (which can be referred to with result, since this is how we defined the callback).

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

You can also process the response before passing it to the callback:

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

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

It's easier to write code using callbacks than it may seem. After all, JavaScript in the browser is heavily event driven (DOM events). Receiving the Ajax response is nothing else but an event.
Difficulties could arise when you have to work with third-party code, but most problems can be solved by just thinking through the application flow.

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



ES2015+: Promises with then()

ES2015+:使用then() 进行Promise

The Promise APIis a new feature of ECMAScript 6 (ES2015), but it has good browser supportalready. There are also many libraries which implement the standard Promises API and provide additional methods to ease the use and composition of asynchronous functions (e.g. bluebird).

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

Promises are containers for futurevalues. When the promise receives the value (it is resolved) or when it is canceled (rejected), it notifies all of its "listeners" who want to access this value.

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

The advantage over plain callbacks is that they allow you to decouple your code and they are easier to compose.

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

Here is a simple example of using a 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).
  });

Applied to our Ajax call we could use promises like this:

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

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

ajax("/echo/json")
  .then(function(result) {
    // Code depending on result
  })
  .catch(function() {
    // An error occurred
  });

Describing all the advantages that promise offer is beyond the scope of this answer, but if you write new code, you should seriously consider them. They provide a great abstraction and separation of your code.

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

More information about promises: HTML5 rocks - JavaScript Promises

有关承诺的更多信息:HTML5 摇滚 - JavaScript 承诺

Side note: jQuery's deferred objects

旁注:jQuery 的延迟对象

Deferred objectsare jQuery's custom implementation of promises (before the Promise API was standardized). They behave almost like promises but expose a slightly different API.

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

Every Ajax method of jQuery already returns a "deferred object" (actually a promise of a deferred object) which you can just return from your function:

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

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

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

Side note: Promise gotchas

旁注:承诺陷阱

Keep in mind that promises and deferred objects are just containersfor a future value, they are not the value itself. For example, suppose you had the following:

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

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

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

This code misunderstands the above asynchrony issues. Specifically, $.ajax()doesn't freeze the code while it checks the '/password' page on your server - it sends a request to the server and while it waits, it immediately returns a jQuery Ajax Deferred object, not the response from the server. That means the ifstatement is going to always get this Deferred object, treat it as true, and proceed as though the user is logged in. Not good.

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

But the fix is easy:

但修复很简单:

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


Not recommended: Synchronous "Ajax" calls

不推荐:同步“Ajax”调用

As I mentioned, some(!) asynchronous operations have synchronous counterparts. I don't advocate their use, but for completeness' sake, here is how you would perform a synchronous call:

正如我所提到的,一些(!)异步操作具有同步对应项。我不提倡使用它们,但为了完整起见,以下是执行同步调用的方式:

Without jQuery

没有 jQuery

If you directly use a XMLHTTPRequestobject, pass falseas third argument to .open.

如果您直接使用XMLHTTPRequest对象,请将其false作为第三个参数传递给.open.

jQuery

jQuery

If you use jQuery, you can set the asyncoption to false. Note that this option is deprecatedsince jQuery 1.8. You can then either still use a successcallback or access the responseTextproperty of the jqXHR object:

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

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

If you use any other jQuery Ajax method, such as $.get, $.getJSON, etc., you have to change it to $.ajax(since you can only pass configuration parameters to $.ajax).

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

Heads up!It is not possible to make a synchronous JSONPrequest. JSONP by its very nature is always asynchronous (one more reason to not even consider this option).

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

回答by Benjamin Gruenbaum

If you're notusing jQuery in your code, this answer is for you

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

Your code should be something along the lines of this:

你的代码应该是这样的:

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

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

Felix Kling did a fine job writing an answer for people using jQuery for AJAX, I've decided to provide an alternative for people who aren't.

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

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

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



What you're facing

你所面临的

This is a short summary of "Explanation of the problem" from the other answer, if you're not sure after reading this, read that.

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

The Ain AJAX stands for asynchronous. That means sending the request (or rather receiving the response) is taken out of the normal execution flow. In your example, .sendreturns immediately and the next statement, return result;, is executed before the function you passed as successcallback was even called.

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

This means when you're returning, the listener you've defined did not execute yet, which means the value you're returning has not been defined.

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

Here is a simple analogy

这是一个简单的类比

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

(Fiddle)

(小提琴)

The value of areturned is undefinedsince the a=5part has not executed yet. AJAX acts like this, you're returning the value before the server got the chance to tell your browser what that value is.

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

One possible solution to this problem is to code re-actively, telling your program what to do when the calculation completed.

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

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

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

This is called CPS. Basically, we're passing getFivean action to perform when it completes, we're telling our code how to react when an event completes (like our AJAX call, or in this case the timeout).

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

Usage would be:

用法是:

getFive(onComplete);

Which should alert "5" to the screen. (Fiddle).

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

Possible solutions

可能的解决方案

There are basically two ways how to solve this:

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

  1. Make the AJAX call synchronous (lets call it SJAX).
  2. Restructure your code to work properly with callbacks.
  1. 使 AJAX 调用同步(我们称之为 SJAX)。
  2. 重构您的代码以使用回调正常工作。

1. Synchronous AJAX - Don't do it!!

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

As for synchronous AJAX, don't do it!Felix's answer raises some compelling arguments about why it's a bad idea. To sum it up, it'll freeze the user's browser until the server returns the response and create a very bad user experience. Here is another short summary taken from MDN on why:

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

XMLHttpRequest supports both synchronous and asynchronous communications. In general, however, asynchronous requests should be preferred to synchronous requests for performance reasons.

In short, synchronous requests block the execution of code... ...this can cause serious issues...

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

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

If you haveto do it, you can pass a flag: Here is how:

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

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

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

2. Restructure code

2. 重构代码

Let your function accept a callback. In the example code foocan be made to accept a callback. We'll be telling our code how to reactwhen foocompletes.

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

So:

所以:

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

Becomes:

变成:

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

Here we passed an anonymous function, but we could just as easily pass a reference to an existing function, making it look like:

这里我们传递了一个匿名函数,但我们也可以很容易地传递对现有函数的引用,使它看起来像:

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

For more details on how this sort of callback design is done, check Felix's answer.

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

Now, let's define foo itself to act accordingly

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

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

(fiddle)

(小提琴)

We have now made our foo function accept an action to run when the AJAX completes successfully, we can extend this further by checking if the response status is not 200 and acting accordingly (create a fail handler and such). Effectively solving our issue.

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

If you're still having a hard time understanding this read the AJAX getting started guideat MDN.

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

回答by cocco

XMLHttpRequest2(first of all read the answers from Benjamin Gruenbaum& Felix Kling)

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

If you don't use jQuery and want a nice short XMLHttpRequest 2 which works on the modern browsers and also on the mobile browsers I suggest to use it this way:

如果您不使用 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()
}

As you can see:

如你看到的:

  1. It's shorter than all other functions Listed.
  2. The callback is set directly (so no extra unnecessary closures).
  3. It uses the new onload (so you don't have to check for readystate && status)
  4. There are some other situations which I don't remember that make the XMLHttpRequest 1 annoying.
  1. 它比列出的所有其他功能都短。
  2. 回调是直接设置的(所以没有额外的不必要的闭包)。
  3. 它使用新的 onload(因此您不必检查 readystate && status)
  4. 还有一些我不记得的其他情况使 XMLHttpRequest 1 烦人。

There are two ways to get the response of this Ajax call (three using the XMLHttpRequest var name):

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

The simplest:

最简单的:

this.response

Or if for some reason you bind()the callback to a class:

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

e.target.response

Example:

例子:

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

Or (the above one is better anonymous functions are always a problem):

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

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

Nothing easier.

没有什么比这更容易的了。

Now some people will probably say that it's better to use onreadystatechange or the even the XMLHttpRequest variable name. That's wrong.

现在有些人可能会说,最好使用 onreadystatechange 甚至 XMLHttpRequest 变量名。那是错误的。

Check out XMLHttpRequest advanced features

查看XMLHttpRequest 高级功能

It supported all *modern browsers. And I can confirm as I'm using this approach since XMLHttpRequest 2 exists. I never had any type of problem on all browsers I use.

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

onreadystatechange is only useful if you want to get the headers on state 2.

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

Using the XMLHttpRequestvariable name is another big error as you need to execute the callback inside the onload/oreadystatechange closures else you lost it.

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



Now if you want something more complex using post and FormData you can easily extend this function:

现在,如果您想要使用 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)
}

Again ... it's a very short function, but it does get & post.

再次......这是一个非常短的功能,但它确实获取和发布。

Examples of usage:

用法示例:

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

Or pass a full form element (document.getElementsByTagName('form')[0]):

或者传递一个完整的表单元素 ( document.getElementsByTagName('form')[0]):

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

Or set some custom values:

或者设置一些自定义值:

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

As you can see I didn't implement sync... it's a bad thing.

正如你所看到的,我没有实现同步......这是一件坏事。

Having said that ... why don't do it the easy way?

话虽如此……为什么不以简单的方式做呢?



As mentioned in the comment the use of error && synchronous does completely break the point of the answer. Which is a nice short way to use Ajax in the proper way?

正如评论中提到的,使用 error && synchronous 确实完全打破了答案的重点。以正确的方式使用 Ajax 的捷径是什么?

Error handler

错误处理程序

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

In the above script, you have an error handler which is statically defined so it does not compromise the function. The error handler can be used for other functions too.

在上面的脚本中,您有一个静态定义的错误处理程序,因此它不会危及功能。错误处理程序也可用于其他功能。

But to really get out an error the onlyway is to write a wrong URL in which case every browser throws an error.

但要真正解决错误,唯一的方法是编写错误的 URL,在这种情况下,每个浏览器都会抛出错误。

Error handlers are maybe useful if you set custom headers, set the responseType to blob array buffer or whatever...

如果您设置自定义标头,将 responseType 设置为 blob 数组缓冲区或其他任何内容,错误处理程序可能很有用...

Even if you pass 'POSTAPAPAP' as the method it won't throw an error.

即使您将 'POSTAPAPAP' 作为方法传递,它也不会抛出错误。

Even if you pass 'fdggdgilfdghfldj' as formdata it won't throw an error.

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

In the first case the error is inside the displayAjax()under this.statusTextas Method not Allowed.

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

In the second case, it simply works. You have to check at the server side if you passed the right post data.

在第二种情况下,它很简单。如果您传递了正确的帖子数据,您必须在服务器端检查。

cross-domain not allowed throws error automatically.

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

In the error response, there are no error codes.

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

There is only the this.typewhich is set to error.

只有this.type设置为错误的 。

Why add an error handler if you totally have no control over errors? Most of the errors are returned inside this in the callback function displayAjax().

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

So: No need for error checks if you're able to copy and paste the URL properly. ;)

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

PS: As the first test I wrote x('x', displayAjax)..., and it totally got a response...??? So I checked the folder where the HTML is located, and there was a file called 'x.xml'. So even if you forget the extension of your file XMLHttpRequest 2 WILL FIND IT. I LOL'd

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



Read a file synchronous

同步读取文件

Don't do that.

不要那样做。

If you want to block the browser for a while load a nice big .txtfile synchronous.

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

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

Now you can do

现在你可以做

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

There is no other way to do this in a non-asynchronous way. (Yeah, with setTimeout loop... but seriously?)

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

Another point is... if you work with APIs or just your own list's files or whatever you always use different functions for each request...

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

Only if you have a page where you load always the same XML/JSON or whatever you need only one function. In that case, modify a little the Ajax function and replace b with your special function.

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



The functions above are for basic use.

以上功能为基本使用。

If you want to EXTEND the function...

如果你想扩展函数...

Yes, you can.

是的你可以。

I'm using a lot of APIs and one of the first functions I integrate into every HTML page is the first Ajax function in this answer, with GET only...

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

But you can do a lot of stuff with XMLHttpRequest 2:

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

I made a download manager (using ranges on both sides with resume, filereader, filesystem), various image resizers converters using canvas, populate web SQL databases with base64images and much more... But in these cases you should create a function only for that purpose... sometimes you need a blob, array buffers, you can set headers, override mimetype and there is a lot more...

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

But the question here is how to return an Ajax response... (I added an easy way.)

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

回答by Benjamin Gruenbaum

If you're using promises, this answer is for you.

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

This means AngularJS, jQuery (with deferred), native XHR's replacement (fetch), EmberJS, BackboneJS's save or any node library that returns promises.

这意味着 AngularJS、jQuery(带有延迟)、原生 XHR 的替换(fetch)、EmberJS、BackboneJS 的保存或任何返回 promise 的节点库。

Your code should be something along the lines of this:

你的代码应该是这样的:

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

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

Felix Kling did a fine job writing an answer for people using jQuery with callbacks for AJAX. I have an answer for native XHR. This answer is for generic usage of promises either on the frontend or backend.

Felix Kling 在为使用带有 AJAX 回调的 jQuery 的人编写答案方面做得很好。我有一个原生 XHR 的答案。此答案适用于前端或后端的 Promise 的一般用法。



The core issue

核心问题

The JavaScript concurrency model in the browser and on the server with NodeJS/io.js is asynchronousand reactive.

浏览器中和带有 NodeJS/io.js 的服务器上的 JavaScript 并发模型是异步反应性的

Whenever you call a method that returns a promise, the thenhandlers are alwaysexecuted asynchronously - that is, afterthe code below them that is not in a .thenhandler.

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

This means when you're returning datathe thenhandler you've defined did not execute yet. This in turn means that the value you're returning has not been set to the correct value in time.

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

Here is a simple analogy for the issue:

这是这个问题的一个简单类比:

    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

The value of datais undefinedsince the data = 5part has not executed yet. It will likely execute in a second but by that time it is irrelevant to the returned value.

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

Since the operation did not happen yet (AJAX, server call, IO, timer) you're returning the value before the request got the chance to tell your code what that value is.

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

One possible solution to this problem is to code re-actively, telling your program what to do when the calculation completed. Promises actively enable this by being temporal (time-sensitive) in nature.

一个可能的解决这个问题是代码重新活跃,告诉你的程序在计算完成后做什么。Promise 本质上是时间性的(时间敏感的),从而积极地实现了这一点。

Quick recap on promises

快速回顾一下承诺

A Promise is a value over time. Promises have state, they start as pending with no value and can settle to:

Promise 是一个随时间变化。Promises 有状态,它们开始时没有任何价值,可以设置为:

  • fulfilledmeaning that the computation completed successfully.
  • rejectedmeaning that the computation failed.
  • 完成意味着计算成功完成。
  • 拒绝意味着计算失败。

A promise can only change states onceafter which it will always stay at the same state forever. You can attach thenhandlers to promises to extract their value and handle errors. thenhandlers allow chainingof calls. Promises are created by using APIs that return them. For example, the more modern AJAX replacement fetchor jQuery's $.getreturn promises.

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

When we call .thenon a promise and returnsomething from it - we get a promise for the processed value. If we return another promise we'll get amazing things, but let's hold our horses.

当我们调用.then一个 promise 并从中返回一些东西时——我们得到了一个对处理过的 value的 promise 。如果我们返回另一个承诺,我们会得到惊人的东西,但让我们抓住我们的马。

With promises

带着承诺

Let's see how we can solve the above issue with promises. First, let's demonstrate our understanding of promise states from above by using the Promise constructorfor creating a delay function:

让我们看看如何用 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);
    });
}

Now, after we converted setTimeout to use promises, we can use thento make it count:

现在,在我们将 setTimeout 转换为使用 promises 之后,我们可以使用then它来计数:

function delay(ms){ // takes amount of milliseconds
  // returns a new promise
  return new Promise(function(resolve, reject){
    setTimeout(function(){ // when the time is up
      resolve(); // change the promise to the fulfilled state
    }, ms);
  });
}

function getFive(){
  // we're RETURNING the promise, remember, a promise is a wrapper over our value
  return delay(100).then(function(){ // when the promise is ready
      return 5; // return the value 5, promises are all about return values
  })
}
// we _have_ to wrap it like this in the call site, we can't access the plain value
getFive().then(function(five){ 
   document.body.innerHTML = five;
});

Basically, instead of returning a valuewhich we can't do because of the concurrency model - we're returning a wrapperfor a value that we can unwrapwith then. It's like a box you can open with then.

基本上,而不是返回一个值,我们不能因为并发模型做-我们返回一个包装的价值,我们可以解开then。它就像一个可以打开的盒子then

Applying this

应用这个

This stands the same for your original API call, you can:

这与您的原始 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`
})

So this works just as well. We've learned we can't return values from already asynchronous calls but we can use promises and chain them to perform processing. We now know how to return the response from an asynchronous call.

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

ES2015 (ES6)

ES2015 (ES6)

ES6 introduces generatorswhich are functions that can return in the middle and then resume the point they were at. This is typically useful for sequences, for example:

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

function* foo(){ // notice the star, this is ES6 so new browsers/node/io only
    yield 1;
    yield 2;
    while(true) yield 3;
}

Is a function that returns an iteratorover the sequence 1,2,3,3,3,3,....which can be iterated. While this is interesting on its own and opens room for a lot of possibility there is one particular interesting case.

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

If the sequence we're producing is a sequence of actions rather than numbers - we can pause the function whenever an action is yielded and wait for it before we resume the function. So instead of a sequence of numbers, we need a sequence of futurevalues - that is: promises.

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

This somewhat tricky but very powerful trick lets us write asynchronous code in a synchronous manner. There are several "runners" that do this for you, writing one is a short few lines of code but is beyond the scope of this answer. I'll be using Bluebird's Promise.coroutinehere, but there are other wrappers like coor Q.async.

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

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

This method returns a promise itself, which we can consume from other coroutines. For example:

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

var main = coroutine(function*(){
   var bar = yield foo(); // wait our earlier coroutine, it returns a promise
   // server call done here, code below executes when done
   var baz = yield fetch("/api/users/"+bar.userid); // depends on foo's result
   console.log(baz); // runs after both requests done
});
main();

ES2016 (ES7)

ES2016 (ES7)

In ES7, this is further standardized, there are several proposals right now but in all of them you can awaitpromise. This is just "sugar" (nicer syntax) for the ES6 proposal above by adding the asyncand awaitkeywords. Making the above example:

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

It still returns a promise just the same :)

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

回答by Nic

You are using Ajax incorrectly. The idea is not to have it return anything, but instead hand off the data to something called a callback function, which handles the data.

您错误地使用了 Ajax。这个想法不是让它返回任何东西,而是将数据交给一个叫做回调函数的东西,它处理数据。

That is:

那是:

function handleData( responseData ) {

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

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

Returning anything in the submit handler will not do anything. You must instead either hand off the data, or do what you want with it directly inside the success function.

在提交处理程序中返回任何东西都不会做任何事情。相反,您必须传递数据,或者直接在成功函数中使用它做您想做的事情。

回答by Hemant Bavle

The simplest solution is create a JavaScript function and call it for the Ajax successcallback.

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

回答by Johannes Fahrenkrug

I will answer with a horrible-looking, hand-drawn comic. The second image is the reason why resultis undefinedin your code example.

我会用一个看起来很可怕的手绘漫画来回答。第二图像是为什么的原因resultundefined在你的代码示例。

enter image description here

enter image description here

回答by Maleen Abewardana

Angular1

角1

For people who are using AngularJS, can handle this situation using Promises.

对于使用AngularJS 的人,可以使用Promises.

Hereit says,

这里说,

Promises can be used to unnest asynchronous functions and allows one to chain multiple functions together.

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

You can find a nice explanation herealso.

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

Example found in docsmentioned below.

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

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

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

Angular2 and Later

Angular2 及更高版本

In Angular2with look at the following example, but its recommendedto use Observableswith Angular2.

Angular2with 看下面的例子,但它推荐使用Observableswith Angular2

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

}

}

You can consume that in this way,

你可以这样消费

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

See the originalpost here. But Typescript does not support native es6 Promises, if you want to use it, you might need plugin for that.

请参阅此处的原始帖子。但是 Typescript 不支持原生 es6 Promises,如果你想使用它,你可能需要插件。

Additionally here is the promises specdefine here.

另外这里是这里定义的承诺规范

回答by T.J. Crowder

Most of the answers here give useful suggestions for when you have a single async operation, but sometimes, this comes up when you need to do an asynchronous operation for eachentry in an array or other list-like structure. The temptation is to do this:

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

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

Example:

例子:

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

The reason that doesn't work is that the callbacks from doSomethingAsynchaven't run yet by the time you're trying to use the results.

不起作用的原因是doSomethingAsync在您尝试使用结果时尚未运行的回调。

So, if you have an array (or list of some kind) and want to do async operations for each entry, you have two options: Do the operations in parallel (overlapping), or in series (one after another in sequence).

因此,如果您有一个数组(或某种列表)并希望对每个条目进行异步操作,您有两种选择:并行(重叠)或串行(按顺序一个接一个)执行操作。

Parallel

平行线

You can start all of them and keep track of how many callbacks you're expecting, and then use the results when you've gotten that many callbacks:

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

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

Example:

例子:

var theArray = [1, 2, 3];
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", results); // E.g., using the results
        }
    });
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(We could do away with expectingand just use results.length === theArray.length, but that leaves us open to the possibility that theArrayis changed while the calls are outstanding...)

(我们可以取消expecting并只使用results.length === theArray.length,但这让我们对theArray在调用未完成时发生变化的可能性持开放态度......)

Notice how we use the indexfrom forEachto save the result in resultsin the same position as the entry it relates to, even if the results arrive out of order (since async calls don't necessarily complete in the order in which they were started).

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

But what if you need to returnthose results from a function? As the other answers have pointed out, you can't; you have to have your function accept and call a callback (or return a Promise). Here's a callback version:

但是如果您需要从函数返回这些结果怎么办?正如其他答案所指出的那样,你不能;你必须让你的函数接受并调用回调(或返回一个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);
});

Example:

例子:

function doSomethingWith(theArray, callback) {
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                callback(results);
            }
        });
    });
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

Or here's a version returning a Promiseinstead:

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

Of course, if doSomethingAsyncpassed us errors, we'd use rejectto reject the promise when we got an error.)

当然,如果doSomethingAsync传递给我们错误,我们会reject在遇到错误时使用拒绝承诺。)

Example:

例子:

function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(Or alternately, you could make a wrapper for doSomethingAsyncthat returns a promise, and then do the below...)

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

If doSomethingAsyncgives you a Promise, you can use Promise.all:

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

If you know that doSomethingAsyncwill ignore a second and third argument, you can just pass it directly to map(mapcalls its callback with three arguments, but most people only use the first most of the time):

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

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

Example:

例子:

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

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

Note that Promise.allresolves its promise with an array of the results of all of the promises you give it when they are all resolved, or rejects its promise when the firstof the promises you give it rejects.

请注意,Promise.all当所有承诺都已解决时,将使用您给它的所有承诺的结果数组来解决其承诺,或者当您给它的第一个承诺拒绝时拒绝其承诺。

Series

系列

Suppose you don't want the operations to be in parallel? If you want to run them one after another, you need to wait for each operation to complete before you start the next. Here's an example of a function that does that and calls a callback with the 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(theArray, function(results) {
    console.log("Results:", results);
});

(Since we're doing the work in series, we can just use results.push(result)since we know we won't get results out of order. In the above we could have used results[index] = result;, but in some of the following examples we don't have an index to use.)

(因为我们是连续工作,所以我们可以使用,results.push(result)因为我们知道我们不会得到乱序的结果。在上面我们可以使用results[index] = result;,但在下面的一些例子中我们没有索引使用。)

Example:

例子:

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(Or, again, build a wrapper for doSomethingAsyncthat gives you a promise and do the below...)

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

If doSomethingAsyncgives you a Promise, if you can use ES2017+ syntax (perhaps with a transpiler like Babel), you can use an asyncfunctionwith for-ofand await:

如果doSomethingAsync给你一个 Promise,如果你可以使用 ES2017+ 语法(也许使用像Babel这样的转译器),你可以使用带有and的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);
});

Example:

例子:

async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

If you can't use ES2017+ syntax (yet), you can use a variation on the "Promise reduce" pattern(this is more complex than the usual Promise reduce because we're not passing the result from one into the next, but instead gathering up their results in an array):

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

Example:

例子:

function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

...which is less cumbersome with ES2015+ arrow functions:

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

Example:

例子:

function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

回答by Francisco Carmona

Have a look at this example:

看看这个例子:

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

As you can see getJokeis returning aresolved promise(it is resolved when returning res.data.value). So you wait until the $http.getrequest is completed and then console.log(res.joke)is executed (as a normal asynchronous flow).

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

This is the plnkr:

这是 plnkr:

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

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

ES6 way (async - await)

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