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

2020/10/25 16:25 · jquery ·  · 0评论

我有一个foo发出异步请求的函数如何从中返回响应/结果foo

我尝试从回调中返回值,并将结果分配给函数内的局部变量并返回该局部变量,但是这些方法均未真正返回响应(它们都返回undefined或变量的初始值是多少result) 。

使用jQueryajax函数的示例

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

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

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

问题

的Ajax代表异步这意味着发送请求(或接收响应)已从正常执行流程中删除。在您的示例中,$.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的执行时间有上限,浏览器会询问用户是否继续执行。

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

在下面的内容中,我们将研究三个互为基础的不同解决方案:

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

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


ES2017 +:承诺 async/await

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

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

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

你可以阅读更多关于asyncawait的MDN。

这是一个基于以上延迟的示例:

// 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来支持较旧的环境


让函数接受回调

函数1传递给函数2时即为回调。函数2就绪时可以调用函数1。在异步过程的上下文中,只要异步过程完成,就会调用回调。通常,结果将传递给回调。

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

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

变成

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

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

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

foo(myCallback);

foo 本身定义如下:

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

callbackfoo在调用它并传递给引用我们传递给的函数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()的承诺

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

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

有关诺言的更多信息: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对象,请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本质上始终是异步的(还有一个甚至不考虑此选项的原因)。

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

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

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

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

Felix Kling很好地为使用jQuery for AJAX的人编写了答案,我决定为那些没有使用jQuery的人提供替代方案。

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


您所面对的

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

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

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

这是一个简单的比喻

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

(小提琴)

The value of a returned is undefined since the a=5 part 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.

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 getFive an 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).

Usage would be:

getFive(onComplete);

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

Possible solutions

There are basically two ways how to solve this:

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

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

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

XMLHttpRequest支持同步和异步通信。但是,一般而言,出于性能方面的考虑,异步请求应比同步请求优先。

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

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

var request = new XMLHttpRequest();
request.open('GET', 'yourURL', false);  // `false` makes the request synchronous
request.send(null);
 
if (request.status === 200) {// That's HTTP for 'ok'
  console.log(request.responseText);
}

2.重组代码

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

所以:

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

成为:

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

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

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

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

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

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

(小提琴)

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.

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

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

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

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

如你看到的:

  1. 它比列出的所有其他功能短。
  2. 回调是直接设置的(因此没有多余的闭包)。
  3. 它使用新的onload(因此您不必检查readystate和&状态)
  4. 还有其他一些我不记得的情况使XMLHttpRequest 1变得令人讨厌。

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

最简单的:

this.response

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

e.target.response

例:

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

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

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

没什么容易的。

现在,有些人可能会说,最好使用onreadystatechange甚至XMLHttpRequest变量名称。错了

查看XMLHttpRequest的高级功能

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

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

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


现在,如果您想使用post和FormData进行更复杂的操作,则可以轻松扩展此功能:

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val},placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.send(d||null)
}

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

用法示例:

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

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 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 only way is to write a wrong URL in which case every browser throws an error.

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

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

即使您将'fdggdgilfdghfldj'作为formdata传递,也不会引发错误。

在第一种情况下,误差是内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响应...(我添加了一种简单的方法。)

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

这意味着AngularJS,jQuery(带有延迟),本机XHR的替换(获取),EmberJS,BackboneJS的保存或任何返回promise的节点库。

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

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

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

Felix Kling很好地为使用jQuery和AJAX回调的人们编写了答案。我对本地XHR有一个答案。这个答案是针对promise在前端或后端的一般用法。


核心问题

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

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

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

这是一个简单的比喻:

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

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

由于尚未执行该操作(AJAX,服务器调用,IO,计时器),因此您将在请求有机会告诉您的代码该值之前返回该值。

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

快速回顾承诺

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

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

一个承诺只能更改一次状态此后它将永远永远保持在同一状态。您可以将then处理程序附加到promise,以提取其值并处理错误。then处理程序允许链接调用。使用返回API的API创建承诺例如,更现代的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`
})

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

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。

这个有点棘手但非常强大的技巧使我们可以以同步方式编写异步代码。有几个“运行器”可以为您完成此任务,编写一小段代码即可,但超出了此答案的范围。我将在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
});

该方法本身返回一个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)

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

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

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

在提交处理程序中返回任何内容都不会做任何事情。取而代之的是,您必须移交数据,或者直接在成功函数中执行所需的操作。

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

我将以恐怖的手绘漫画来回答。第二图像是为什么的原因resultundefined在你的代码示例。

在此处输入图片说明

角度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.

Angular2及更高版本

Angular2同看看下面的例子,但其推荐给使用Observables具有Angular2

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

}

你可以这样消耗掉

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

此处查看原始帖子。但是Typescript不支持本机es6 Promises,如果要使用它,则可能需要插件。

另外,这里是在这里定义的Promise规范

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

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

例:

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

例:

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

例:

或以下是返回的版本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在遇到错误时拒绝诺言。)

例:

(或者,您可以为此包装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);
});

例:

请注意,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;,但是在以下某些示例中,我们没有索引使用。)

例:

(或者再次构建一个包装器,doSomethingAsync以给您一个承诺,然后执行以下操作...)

如果doSomethingAsync给您一个承诺,如果您可以使用ES2017 +语法(也许使用像Babel这样的编译器),则可以将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);
});

例:

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

例:

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

例:

看一下这个例子:

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正在返回一个已解决的Promise(在返回时已解决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);
  });
})();

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

因此,如果您使用的是Angular,React或任何其他通过两种方式进行数据绑定的框架存储概念的此问题仅为您解决,因此,简单来说,您的结果undefined处于第一阶段,因此您已经result = undefined获得了数据,那么一旦获得结果,它就会被更新并分配给您的Ajax调用响应的新值...

但是,如何才能做到这一点 JavaScriptjQuery,如您在此问题中所提出的呢?

您可以使用callbackpromise和最近可观察回调为您处理它,例如在promise中,我们有一些类似success()或的函数then(),当您的数据为您准备好后将执行该函数,与callback或函数,与observable上的subscription函数

例如,在您使用的情况下 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(); //call happens here
});

有关更多信息,请参阅有关promiseobservable的信息这是执行异步操作的新方法。

在挣扎着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秒后执行一个函数以返回“ wohoo”。但是正如您所看到的,分配给bar的值是foo()立即返回的内容,它什么也没有,就是just undefined

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

让我们问一下函数PROMISEPromise的确是关于它的含义:它意味着该函数保证您提供将来获得的任何输出。因此,让我们来看一下上面的小问题:

function foo(){
   return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
    setTimeout ( function(){ 
      // promise is RESOLVED , when 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的调用等,您可以使用promise来 resolve对值(打算返回)。因此,简而言之,您解决价值而不是返回在异步函数中

更新(承诺异步/等待)

除了then/catch用于兑现承诺之外,还存在另一种方法。这个想法是识别一个异步函数,然后等待承诺解决,然后再转到下一行代码。仍然只是promises内幕,但是采用了不同的句法方法。为了使事情更清楚,您可以在下面找到一个比较:

然后/捕获版本:

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

异步/等待版本:

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

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

这是一个相同的示例:

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

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

尽管promise和callback在许多情况下都可以正常工作,但表达类似以下内容很麻烦:

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
}

您可以在此处签出项目

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

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

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

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

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

对于每一项,一个新的Promise将触发一个block- ExecutionBlock,解析结果,基于结果数组(即Spotifyuser对象的列表)安排一组新的Promise ExecutionProfileBlock异步执行新的HTTP调用

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

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

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

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

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

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

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

2017年答案:现在,您可以在当前的每个浏览器和节点中完全执行所需的操作

这很简单:

  • 退还承诺
  • 使用'await',它将告诉JavaScript等待将被解析为值的承诺(例如HTTP响应)
  • “ async”关键字添加到父函数

这是您的代码的有效版本:

(async function(){

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

})()

当前所有浏览器和节点8中都支持await

Js是单线程的。

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

1)事件循环

2)Web API

3)事件队列

事件循环永远运行,即无限循环。事件队列是将所有功能推送到某个事件(例如单击)的地方,这是逐个执行的,并放入事件循环中,该循环执行该功能并自行准备对于第一个函数执行完后的下一个函数,这意味着直到事件循环中执行该函数的队列中的函数才开始执行一个函数。

现在让我们认为我们在队列中推送了两个函数,一个是从服务器获取数据,另一个是利用该数据。我们先在队列中推送了serverRequest()函数,然后是utiliseData()函数。serverRequest函数进入事件循环并调用服务器,因为我们永远不知道从服务器获取数据将花费多少时间,因此预计此过程将花费一些时间,因此我们忙于事件循环,从而挂起了页面,这就是Web API发挥作用,它从事件循环中获取此功能,并与服务器释放事件循环,以便我们可以从队列中执行下一个功能。队列中的下一个功能是utiliseData(),它进入了循环,但由于没有可用数据,它进入了浪费,下一个函数的执行一直持续到队列结束(这称为异步调用,即我们可以做其他事情直到获得数据)

假设我们的serverRequest()函数在代码中有一个return语句,当我们从服务器Web API取回数据时,它将在队列末尾将其压入队列。由于它在队列末尾被推送,因此我们无法利用其数据,因为队列中没有剩余功能可以利用此数据。因此,不可能从异步调用返回某些内容。

因此,解决方案是回调Promise

来自此处答案之一的图像,正确说明了回调的用法...
我们将函数(利用从服务器返回的数据的函数)提供给函数调用服务器。

打回来

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

Javscript.info回调

您可以使用此自定义库(使用Promise编写)来进行远程调用。

function $http(apiConfig) {
    return new Promise(function (resolve, reject) {
        var client = new XMLHttpRequest();
        client.open(apiConfig.method, apiConfig.url);
        client.send();
        client.onload = function () {
            if (this.status >= 200 && this.status < 300) {
                // Performs the function "resolve" when this.status is equal to 2xx.
                // Your logic here.
                resolve(this.response);
            }
            else {
                // Performs the function "reject" when this.status is different than 2xx.
                reject(this.statusText);
            }
        };
        client.onerror = function () {
            reject(this.statusText);
        };
    });
}

简单用法示例:

$http({
    method: 'get',
    url: 'google.com'
}).then(function(response) {
    console.log(response);
}, function(error) {
    console.log(error)
});

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

如果底层功能被承诺

nsynjs将顺序评估所有promise,并将promise结果放入data属性中:

function synchronousCode() {

    var getURL = function(url) {
        return window.fetch(url).data.text().data;
    };
    
    var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
    console.log('received bytes:',getURL(url).length);
    
};

nsynjs.run(synchronousCode,{},function(){
    console.log('synchronousCode done');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>

如果基础功能不明确

步骤1.将带有回调的函数包装到nsynjs感知的包装器中(如果它具有承诺版本,则可以跳过此步骤):

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

步骤2.将同步逻辑放入功能中:

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

步骤3.通过nsynjs以同步方式运行函数:

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

Nsynjs将逐步评估所有运算符和表达式,以防某些慢速函数的结果尚未就绪时暂停执行。

此处有更多示例:https : //github.com/amaksr/nsynjs/tree/master/examples

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

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

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

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

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

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

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

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

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

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

示例:jQuery推迟实现以处理多个请求

var App = App || {};

App = {
    getDataFromServer: function(){

      var self = this,
                 deferred = $.Deferred(),
                 requests = [];

      requests.push($.getJSON('request/ajax/url/1'));
      requests.push($.getJSON('request/ajax/url/2'));

      $.when.apply(jQuery, requests).done(function(xhrResponse) {
        return deferred.resolve(xhrResponse.result);
      });
      return deferred;
    },

    init: function(){

        this.getDataFromServer().done(_.bind(function(resp1, resp2) {

           // Do the operations which you wanted to do when you
           // get a response from Ajax, for example, log response.
        }, this));
    }
};
App.init();

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

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

考虑一个例子。您打电话给送牛奶的人,点些牛奶。到时,您想将其放入咖啡中。您现在不能将牛奶放入咖啡中,因为现在还没有。您必须等待它出现后才能将其放入咖啡中。换句话说,以下操作无效:

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

因为JS有没有办法知道它需要等待order_milk完成它执行之前put_in_coffee换句话说,它不知道order_milk异步的-直到将来某个时候才导致牛奶。JS和其他声明性语言无需等待即可执行另一个语句。

针对此问题的经典JS方法利用JS支持将函数作为可传递的一流对象这一事实的优势,是将函数作为参数传递给异步请求,并在异步请求完成后将其调用它的任务在将来的某个时候。那就是“回调”方法。看起来像这样:

order_milk(put_in_coffee);

order_milk开始,订购牛奶,然后只有当牛奶到达时,它才会调用put_in_coffee

这种回调方法的问题在于,它会污染报告结果的函数的正常语义return相反,函数不得通过调用作为参数给出的回调来报告其结果。同样,在处理较长的事件序列时,此方法可能很快变得笨拙。例如,假设我要等待牛奶放入咖啡中,然后再执行第三步,即喝咖啡。我最终需要写这样的东西:

order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }

我要传递给put_in_coffee牛奶的位置,以及传递牛奶后要drink_coffee执行的动作()。这样的代码很难编写,读取和调试。

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

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

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

输入承诺

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

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

order_milk() . then(put_in_coffee)

这样的优点之一是我们可以将它们串在一起以创建将来发生的序列(“链接”):

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

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

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

实际上,我们所做的只是在return对的调用中添加了$.ajax之所以可行,是因为jQuery$.ajax已经返回了一种类似于Promise的东西。(在实践中,我们无需包装细节,而是希望包装此调用,以便返回真实的诺言,或使用其他方法替代$.ajax诺言。)现在,如果我们要加载文件并等待其完成并然后做点什么,我们可以简单地说

get_data() . then(do_something)

例如,

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

使用promise时,我们最终将大量函数传递到then,因此使用更紧凑的ES6风格的箭头函数通常会有所帮助:

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

async关键字

但是对于以同步方式编写代码和以异步方式编写完全不同的代码仍然存在一些含糊的不满。对于同步,我们写

a();
b();

但是如果a是异步的,那么我们必须写

a() . then(b);

上面我们说过:“ JS无法知道它需要等待第一个调用完成才能执行第二个调用”。那岂不是很好,如果有一些方法来告诉JS呢?事实证明,await在特殊类型的函数(称为“异步”函数)中使用了关键字。该功能是ES即将发布的版本的一部分,但已经提供了正确的预设的转译器(例如Babel)中已可用。这使我们可以简单地编写

async function morning_routine() {
  var milk   = await order_milk();
  var coffee = await put_in_coffee(milk);
  await drink(coffee);
}

就您而言,您将可以编写如下内容

async function foo() {
  data = await get_data();
  console.log(data);
}

简短答案:您的foo()方法立即返回,而函数返回后,调用将$ajax()异步执行问题是一旦异步调用返回,如何或在何处存储结果。

在该线程中已经给出了几种解决方案。也许最简单的方法是将一个对象传递给该foo()方法,并在异步调用完成后将结果存储在该对象的成员中。

function foo(result) {
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;   // Store the async result
        }
    });
}

var result = { response: null };   // Object to hold the async result
foo(result);                       // Returns before the async completes

请注意,对的调用foo()仍然不会返回任何有用的信息。但是,异步调用的结果现在将存储在中result.response

成功使用callback()内部函数foo()以这种方式尝试。它简单易懂。  

var lat = "";
var lon = "";
function callback(data) {
    lat = data.lat;
    lon = data.lon;
}
function getLoc() {
    var url = "http://ip-api.com/json"
    $.getJSON(url, function(data) {
        callback(data);
    });
}

getLoc();

使用承诺

这个问题最完美的答案是使用Promise

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

用法

ajax("GET", "/test", "acrive=1").then(function(result) {
    // Code depending on result
})
.catch(function() {
    // An error occurred
});

可是等等...!

使用诺言有问题!

我们为什么要使用自己的自定义Promise?

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

Uncaught ReferenceError: Promise is not defined

因此,如果未定义ES3,我决定对下面的js编译器实现自己的Promise类只需在您的主要代码之前添加此代码,然后安全地使用Promise!

if(typeof Promise === "undefined"){
    function _classCallCheck(instance, Constructor) {
        if (!(instance instanceof Constructor)) { 
            throw new TypeError("Cannot call a class as a function"); 
        }
    }
    var Promise = function () {
        function Promise(main) {
            var _this = this;
            _classCallCheck(this, Promise);
            this.value = undefined;
            this.callbacks = [];
            var resolve = function resolve(resolveValue) {
                _this.value = resolveValue;
                _this.triggerCallbacks();
            };
            var reject = function reject(rejectValue) {
                _this.value = rejectValue;
                _this.triggerCallbacks();
            };
            main(resolve, reject);
        }
        Promise.prototype.then = function then(cb) {
            var _this2 = this;
            var next = new Promise(function (resolve) {
                _this2.callbacks.push(function (x) {
                    return resolve(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.catch = function catch_(cb) {
            var _this2 = this;
            var next = new Promise(function (reject) {
                _this2.callbacks.push(function (x) {
                    return reject(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.triggerCallbacks = function triggerCallbacks() {
            var _this3 = this;
            this.callbacks.forEach(function (cb) {
                cb(_this3.value);
            });
        };
        return Promise;
    }();
}

问题是:

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

可以解释为:

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

解决方案是避免回调,并结合使用Promisesasync / await

我想举一个Ajax请求的例子。

(尽管它可以用Javascript编写,但我更喜欢用Python编写,然后使用Transcrypt将其编译为Javascript将其。这已经足够清楚了。)

让我们先启用JQuery的使用,有$可作为S

__pragma__ ('alias', 'S', '$')

定义一个返回Promise的函数,在本例中为Ajax调用:

def read(url: str):
    deferred = S.Deferred()
    S.ajax({'type': "POST", 'url': url, 'data': { },
        'success': lambda d: deferred.resolve(d),
        'error': lambda e: deferred.reject(e)
    })
    return deferred.promise()

使用异步代码,就好像它是同步的

async def readALot():
    try:
        result1 = await read("url_1")
        result2 = await read("url_2")
    except Exception:
        console.warn("Reading a lot failed")

当然,有许多方法,例如同步请求,promise,但是根据我的经验,我认为您应该使用回调方法。Java的异步行为是很自然的。因此,您的代码片段可以重写一些:

function foo() {
    var result;

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

    return result;
}

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

在阅读了这里的所有答复以及我的经验之后,我想恢复callback, promise and async/awaitJavaScript中异步编程的细节

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

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

但是,如果在下面的示例中必须使用许多嵌套的回调,那么代码重构将非常糟糕。

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

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

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

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

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

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

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

异步函数为我们提供了简洁明了的语法,使我们可以编写更少的代码来实现与诺言相同的结果。Async / Await看起来类似于同步代码,并且同步代码更容易读写。要捕获Async / Await中的错误,我们可以使用block try...catch在这里,您无需编写Promise语法的.then()链。

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

getExchangeRate();

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

除了了解代码之外,还有2个概念是理解JS如何处理回调和异步性的关键。(哪怕一个字?)

事件循环和并发模型

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

概括地说,事件循环就像项目管理器一样,它一直在侦听要运行的任何功能,并在队列和堆栈之间进行通信。

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

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

 1. call foo.com/api/bar using foobarFunc
 2. Go perform an infinite loop
 ... and so on

当这些消息之一要执行时,它会从队列中弹出消息并创建一个堆栈,而该堆栈就是JS执行该消息中的指令所需的一切。因此,在我们的示例中,它被告知致电foobarFunc

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

因此,foobarFunc需要执行的所有操作(在我们的示例中 anotherFunction)都将被压入堆栈。执行,然后被遗忘-事件循环将移至队列中的下一件事(或侦听消息)

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

什么时候会运行

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

最大的问题是,它将何时获得响应?答案是我们不知道-事件循环正在等待该消息说“嘿,我快跑”。如果JS只是同步地等待该消息,则您的应用程序将冻结并且将无法正常运行。因此,JS在等待消息添加回队列的同时继续执行队列中的下一个项目。

这就是为什么在异步功能中我们使用了称为callbacks的东西从字面上看,这有点像一个承诺就像我保证在某个时候返回某些内容一样, jQuery使用称为deffered.done deffered.fail和的特定回调deffered.always你可以在这里看到他们

因此,您需要做的是传递一个函数,该函数应在传递给它的数据的某个点执行。

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

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

因此大多数时候(但并非总是如此),您foo不会foo()

希望这会有所道理。当您遇到这样令人困惑的事情时,我强烈建议您完整阅读文档以至少了解它。这将使您成为更好的开发人员。

本文地址:http://jquery.askforanswer.com/ruhecongyibudiaoyongfanhuixiangying.html
文章标签: ,   ,   ,  
版权声明:本文为原创文章,版权归 admin 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!