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

javascript jquery ajax asynchronous


我有一个函数 foo ,它发出Ajax请求。 如何返回 foo 的响应?

我尝试从 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`.




Answer 1 Felix Kling


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

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

问题是

Ajax中A代表异步 。 这意味着发送请求(或更确切地说接收响应)已从正常执行流程中删除。 在您的示例中, $.ajax 立即返回,下一条语句 return result; 会在调用 success 回调函数之前执行。

这里有一个比喻,希望能让同步流和异步流的区别更加清晰。

Synchronous

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

当你进行包含 "正常 "代码的函数调用时也是如此。

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

var item = findItem();

// Do something with item
doSomethingElse();

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

Asynchronous

您出于相同的原因再次致电给您的朋友。 但是这次您告诉他您很着急,他应该用您的手机给您回电 。 您挂断电话,离开房子,然后按计划做。 一旦您的朋友给您回电,您就可以处理他提供给您的信息。

这正是你做Ajax请求时的情况。

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

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


Solution(s)

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

你问为什么不好?

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

这些都是非常糟糕的用户体验。用户将无法辨别出是否一切正常工作。而且,对于连接速度慢的用户来说,效果会更差。

下面我们就来看看这三种不同的解决方案,都是建立在对方的基础上的。

  • async/await 承诺 (ES2017 +,如果使用转译器或再生器,则在较旧的浏览器中可用)
  • 回调 (在节点中受欢迎)
  • then() 的承诺 (ES2015 +,如果您使用许多promise库之一,则在较旧的浏览器中可用)

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


ES2017 +:带有 async/await 承诺

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

async/await 建立在promise之上: async 函数总是返回promise。 await “解开”承诺,并导致承诺被解决的价值,或者如果承诺被拒绝,则抛出错误。

重要提示:您只能在 async 函数内使用 await 。 目前,尚不支持顶层 await ,因此您可能必须进行异步IIFE( 立即调用函数表达式 )才能启动 async 上下文。

您可以阅读有关 async 并在MDN上 await 更多信息。

这里有一个例子,是在上面的延迟基础上建立起来的。

// Using 'superagent' which will return a promise.
var superagent = require('superagent')

// This is isn't declared as `async` because it already returns a promise
function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}


async function getAllBooks() {
  try {
    // GET a list of book IDs of the current user
    var bookIDs = await superagent.get('/user/books');
    // wait for 3 seconds (just for the sake of this example)
    await delay();
    // GET information about each book
    return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
  } catch(error) {
    // If any of the awaited promises was rejected, this catch block
    // would catch the rejection reason
    return null;
  }
}

// Start an IIFE to use `await` at the top level
(async function(){
  let books = await getAllBooks();
  console.log(books);
})();

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


让函数接受回调

回调只是一个传递给另一个函数的函数。该另一个函数可以随时调用传递的函数。在异步进程的上下文中,每当异步进程完成时,回调就会被调用。通常情况下,结果会被传递给回调。

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

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

becomes

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 )的使用和组合。

承诺是未来价值的容器。 当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).
  });

应用到我们的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
  });

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

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

侧面说明:jQuery的递延对象

延迟对象是jQuery的promise的自定义实现(在Promise API标准化之前)。 它们的行为几乎像promise,但是暴露了稍微不同的API。

jQuery的每一个Ajax方法都已经返回一个 "递延对象"(实际上是一个递延对象的承诺),你可以直接从你的函数中返回。

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

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

侧面说明:承诺的问题

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

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

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

此代码误解了上述异步问题。 具体来说, $.ajax() 在检查服务器上的“ / 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 对象, .open false 作为第三个参数传递给.open 。

jQuery

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

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

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

小心! 不可能发出同步JSONP请求。 JSONP本质上始终是异步的(还有一个甚至不考虑此选项的原因)。




Answer 2 Benjamin Gruenbaum


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

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


你要面对的是什么

这是另一个答案中的 "问题解析 "的简单总结,如果你看完后不确定,那就看这个。

AJAX中的A代表异步 。 这意味着发送请求(或更确切地说接收响应)已从正常执行流程中删除。 在您的示例中, .send 立即返回,下一条语句 return result; 会在调用 success 回调函数之前执行。

这意味着当你返回的时候,你定义的监听器还没有执行,这意味着你要返回的值还没有被定义。

这里有一个简单的比喻

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

(Fiddle)

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

解决此问题的一种可能方法是重新编写代码,告诉您的程序在完成计算后要做什么。

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

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

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

用法将是:

getFive(onComplete);

应该在屏幕上提示“ 5”。 (提琴)

可能的解决办法

基本上有两种方法如何解决。

  1. 使AJAX调用同步(让我们称它为SJAX)。
  2. 重构你的代码,使之与回调正常工作。

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

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

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

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

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

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

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

2.2.改组代码

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

So:

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

Becomes:

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

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

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

关于这类回调设计的详细情况,请看Felix的回答。

现在,让我们来定义一下foo本身,让其相应的行动。

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

(fiddle)

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

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




Answer 3 cocco


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

如果你不使用jQuery,而想要一个漂亮的XMLHttpRequest 2,在现代浏览器和手机浏览器上都能正常工作,我建议用这个方法。

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

如你所见。

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

有两种方法可以获得这个Ajax调用的响应(三是使用XMLHttpRequest var名)。

最简单的。

this.response

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

e.target.response

Example:

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

再次...这是一个非常短的函数,但它确实可以获取和发布。

用例:

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,在这种情况下,每个浏览器都会抛出错误。

如果你设置了自定义标题,将响应类型设置为blob数组缓冲区或其他什么的,那么错误处理程序也许会很有用......

即使你通过'POSTAPAPAPAP'作为方法,它也不会抛出一个错误。

即使你把'fdggdgdgilfdghfldj'作为formdata,它也不会抛出一个错误。

在第一种情况下,错误是在 this.statusText 下的 displayAjax() 内部,因为 Method not Allowed

在第二种情况下,简单的说就是可以。你必须在服务器端检查你是否通过了正确的帖子数据。

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

在错误回复中,没有错误代码。

只有 this.type 设置为error。

如果您完全无法控制错误,为什么还要添加错误处理程序? 大多数错误都在回调函数 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或者只是你自己的列表文件或者其他什么,你总是对每个请求使用不同的函数.....

只有当你有一个页面总是加载相同的XMLJSON或其他什么你只需要一个函数。在这种情况下,修改一下Ajax函数,把b替换成你的特殊函数。


以上功能为基本使用。

如果你想扩展功能......

是的,你可以。

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

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

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

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




Answer 4 Benjamin Gruenbaum


如果你在使用承诺,这个答案是给你的。

这意味着AngularJS、jQuery(带递延)、原生XHR的替换(fetch)、EmberJS、BackboneJS的save或任何返回承诺的节点库。

你的代码应该是这样的。

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的答案。这个答案是针对前端或后端的承诺的通用用法。


核心问题

浏览器和带有NodeJS / 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

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

由于操作还没有发生(AJAX、服务器调用、IO、定时器),所以在请求还没有机会告诉你的代码这个值是什么之前,你就返回了这个值。

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

关于承诺的快速回顾

承诺是一段时间价值 。 承诺具有状态,它们以没有价值的待处理状态开始,可以解决:

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

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

当我们调用 .then 承诺并从中返回某些内容时-我们获得了处理后价值的承诺。 如果我们再次兑现诺言,我们将获得惊人的成就,但让我们坚持不懈。

有了诺言

让我们看看如何用诺言解决上述问题。 首先,让我们通过使用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, we can't access the plain value
getFive().then(function(five){ 
   document.body.innerHTML = five;
});

基本上,不是返回由于并发模型而无法执行的 ,而是返回一个包装器 ,该包装器可以使用 then 进行包装 。 就像是一个可以用 then 打开的盒子。

应用这一点

这对你原来的API调用是一样的,你可以。

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

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

所以这个也是一样可以的。我们已经知道了我们不能从已经的异步调用中返回值,但我们可以使用承诺并将其链式化来执行处理。现在我们知道了如何从异步调用中返回响应。

ES2015(ES6)

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

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

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

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

这个有点棘手但非常强大的技巧使我们可以以同步方式编写异步代码。 有几个“运行器”可以为您执行此操作,编写一小段代码即可,但超出了此答案的范围。 我将在这里使用Bluebird的 Promise.coroutine ,但还有其他包装器,例如 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
});

这个方法返回一个承诺本身,我们可以从其他的coroutines中消耗掉。比如说。

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)

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

它仍然会返回一个承诺,只是同样的承诺:)




Answer 5 Nic


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

在提交处理程序中返回任何东西都不会起到任何作用。相反,你必须把数据交给别人,或者直接在成功函数中做你想做的事情。