提问



我有一个函数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`.

最佳参考



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

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



问题



Ajax中的 A 代表异步。这意味着发送请求(或者更确切地说是接收响应)将从正常执行流程中取出。在你的例子中,$.ajax立即返回,下一个语句return result;在你传递给success回调的函数被执行之前被执行。[229] [230]


这是一个类比,希望使同步和异步流之间的区别更加清晰:


同步



想象一下,你打电话给朋友,让他为你寻找一些东西。虽然可能需要一段时间,但你要等电话并凝视太空,直到你的朋友给你你需要的答案。


当您进行包含普通代码的函数调用时,会发生同样的情况:


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


而不是等待响应,执行立即继续执行Ajax调用之后的语句。为了最终获得响应,您提供了一个在收到响应后调用的函数,一个回调(注意一些事情?回调?)。调用之后的任何语句都会在调用回调之前执行。





将(S)



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


你问为什么这么糟糕?


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


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


在下文中,我们将看看三种不同的解决方案,它们都是相互建立的:



  • 承诺async/await (ES2017 +,如果您使用转发器或再生器,则可在较旧的浏览器中使用)

  • 回调(在节点中很受欢迎)

  • 承诺then() (ES2015 +,如果您使用众多承诺库中的一个,则可在旧版浏览器中使用)



当前浏览器和节点7 +。都可以使用这三个





ES2017 +:承诺async/await



2017年发布的新ECMAScript版本为异步函数引入了语法级支持。在asyncawait的帮助下,您可以在同步样式中编写异步。不过没错:代码仍然是异步的,但它更容易阅读/理解。[231]


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


重要事项:您只能在async功能中使用await。这意味着,在最高级别,您仍然必须直接与承诺一起工作。


你可以在MDN上阅读更多关于asyncawait的内容。[232] [233]


这是一个建立在上面延迟之上的例子:


// 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 a second (just for the sake of this example)
    await delay(1000);
    // 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;
  }
}

// Async functions always return a promise
getAllBooks()
  .then(function(books) {
    console.log(books);
  });


较新的浏览器和节点版本支持async/await。您还可以通过在再生器(或使用再生器的工具,如Babel)的帮助下将代码转换为ES5来支持旧环境。[234] [235] [236] [237]





让函数接受回调



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


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


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





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


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


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

foo(myCallback);


foo本身定义如下:


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


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


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


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


使用回调编写代码比使用它看起来更容易。毕竟,浏览器中的JavaScript是大量事件驱动的(DOM事件)。接收Ajax响应只不过是一个事件。

当您必须使用第三方代码时可能会出现困难,但大多数问题可以通过思考应用程序流来解决。





ES2015 +:承诺与then()



Promise API是ECMAScript 6(ES2015)的新功能,但它已经具有良好的浏览器支持。还有许多库实现了标准的Promises API,并提供了其他方法来简化异步函数的使用和组合(例如bluebird)。[238] [239] [240] [241]


Promise是 future 值的容器。当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调用,我们可以使用这样的promises:


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 rock - JavaScript Promises [242]


旁注:jQuery的延迟对象



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


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


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

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


旁注:承诺陷阱



请记住,promises和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
}


此代码误解了上述异步问题。具体来说,$.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[244] [245]


的jQuery



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


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


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


抬头!无法发出同步JSONP请求。 JSONP本质上总是异步的(甚至不考虑这个选项的另一个原因)。

其它参考1


如果您在代码中使用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的人写了一个很好的答案,我已经决定为那些没有人的人提供替代方案。


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





你面对的是什么



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


AJAX中的 A 代表异步。这意味着发送请求(或者更确切地说是接收响应)将从正常执行流程中取出。在你的例子中,.send立即返回,下一个语句return result;在你传递给success回调的函数被执行之前执行。[250]


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


这是一个简单的比喻


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


(小提琴)[251]


由于a=5部分尚未执行,a返回的值为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调用,或者在这种情况下是超时)。[252]]]


用法是:


getFive(onComplete);


哪个应警告5到屏幕。 (小提琴)。[253]


可能的解决方案



基本上有两种解决方法:



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

  2. 使用回调重构代码以使其正常工作。



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



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



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

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



如果你有来做,你可以传递一个标志:这是如何:[254]


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


(小提琴)[255]


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


如果您仍然很难理解这一点,请阅读MDN的AJAX入门指南。[256]

其它参考2


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


如果你不使用jQuery并想要一个很好的简短的XMLHttpRequest 2,它适用于现代浏览器,也适用于移动浏览器,我建议用这种方式:


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


如你看到的:



  1. 它比列出的所有其他功能都短。

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

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

  4. 还有其他一些情况,我不记得让XMLHttpRequest 1烦人。



有两种方法可以获得此Ajax调用的响应(三种使用XMLHttpRequest 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高级功能[258]


它支持所有*现代浏览器。我可以确认,因为我使用这种方法,因为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)
}


再说一次......这是一个非常短暂的功能,但它确实得到了& post。


用法示例:


x(url, callback); // By default it's get so no need to set
x(url, callback, 'post', {'key': 'val'}); // No need to set post data


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


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


或者设置一些自定义值:


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


如你所见,我没有实现同步......这是一件坏事。


话虽如此......为什么不这么简单呢?





如评论中所述,使用错误&&同步确实完全打破了答案的要点。哪个是以正确的方式使用Ajax的简短方法?


错误处理程序


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

function error(e){
  console.log('--Error--', this.type);
  console.log('this: ', this);
  console.log('Event: ', e)
}
function displayAjax(e){
  console.log(e, this);
}
x('WRONGURL', displayAjax);


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


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


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


即使你传递POSTAPAPAP作为方法它也不会抛出错误。


即使你传递fdggdgilfdghfldj作为formdata它也不会抛出错误。


在第一种情况下,误差在this.statusText下的displayAjax()内作为Method not Allowed


在第二种情况下,它只是起作用。如果您传递了正确的帖子数据,则必须在服务器端进行检查。


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


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


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


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


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


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





同步读取文件


不要这样做。


如果你想阻止浏览器一段时间加载一个漂亮的大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填充websql数据库等等......但在这些情况下,您应该仅为此目的创建一个函数...有时你需要一个blob,数组缓冲区,你可以设置标题,覆盖mimetype,还有更多......


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

其它参考3


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



这意味着AngularJS,jQuery(带有延迟),本机XHR的替换(fetch),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的答案。这个答案是对前端或后端的承诺的一般用法。





核心问题



浏览器和具有NodeJS/io.js的服务器上的JavaScript并发模型是异步和被动。


每当你调用一个返回一个promise的方法时,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



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


在提交处理程序中返回任何内容都不会执行任何操作。您必须切换数据,或者直接在成功函数内执行您想要的操作。

其它参考4


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


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

其它参考5


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


[259]

其它参考6


Angular1



对于使用AngularJS的人,可以使用Promises来处理这种情况。[260]


在这里说,[261]



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



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


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


  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中查看以下示例,但建议将ObservablesAngular2一起使用。[264]


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


另外这里是这里定义的承诺规范。[267]

其它参考7


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


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


例:




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

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}

.as-console-wrapper {
  max-height: 100% !important;
}



看一下这个例子:


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

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

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

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


正如您所看到的,getJoke 返回已解决的承诺(在返回res.data.value时会解析)。所以你要等到 $ http.get 请求完成后再执行 console.log(res.joke)(作为正常的异步流程)。


这是plnkr:


http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/[268]

其它参考8


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


这是一个相同的例子:


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对象在异步操作期间存储该值。这样即使在异步作业之后也可以获得结果。


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

其它参考9


这是两种方式数据绑定的地方之一,在许多新的JavaScript框架中使用它将对你有很大帮助...


因此,如果您使用 Angular,React 或任何其他框架两种方式进行数据绑定,这个问题只是为您修复,所以简单来说,您的结果是[[undefined在第一阶段,所以你在收到数据之前得到result = undefined,然后一旦你得到结果,它就会更新并被分配给你的Ajax调用响应的新值...


但是你如何在纯粹的 javascript jQuery 中做到这一点,例如你在这个问题中提到的那样?


您可以使用回调承诺以及最近可观察来为您处理它,例如在promises中我们有一些功能,如success()或then()将在您的数据准备就绪时执行,与可观察上的回调或订阅功能相同。


例如,在您使用 jQuery 的情况下,您可以执行以下操作:


$(document).ready(function(){
    function foo() {
        $.ajax({url: "api/data", success: function(data){
            fooDone(data); //after we have data, we pass it to fooDone
        }});
    };

    function fooDone(data) {
        console.log(data); //fooDone has the data and console.log it
    };

    foo(); //call happens here
});


有关承诺 observables 的更多信息,这些是执行此异步内容的新方法。

其它参考10


虽然承诺和回调在许多情况下都能很好地发挥作用,但在后面表达类似的东西是痛苦的:


if (!name) {
  name = async1();
}
async2(name);


你最终会通过async1;检查name是否未定义,并相应地调用回调。


async1(name, callback) {
  if (name)
    callback(name)
  else {
    doSomething(callback)
  }
}

async1(name, async2)


虽然在小例子中它是 okay 但是当你遇到很多类似的案例和错误处理时会很烦人。


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
}


你可以在这里查看项目。[269]

其它参考11


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


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

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

其它参考12


我写的以下示例说明了如何操作



  • 处理异步HTTP调用;

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

  • 使用Promise模式;

  • 使用Promise.All模式加入多个HTTP调用;



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


语境。该示例是查询Spotify Web API端点,以便为给定的查询字符串集搜索playlist个对象:[271]


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


对于每个项目,新的Promise将触发一个块 - ExecutionBlock​​]],解析结果,根据结果数组安排一组新的promises,即Spotify user对象列表并执行新的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" />



您可以使用此自定义库(使用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)
});

其它参考13



  Js是单线程的。



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


1)事件循环


2)Web API


3)事件队列


Event Loop永远运行,即一种无限循环.Event Queue是在某些事件上推送所有函数的地方(例如:click)这是逐个执行队列并放入Event循环执行此函数并准备自己执行第一个函数后执行下一个函数。这意味着执行一个函数不会启动,直到队列中的函数在事件循环中执行。


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


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


因此,解决方法是回调或承诺。


来自其中一个答案的图片,正确解释回调使用......
我们将功能(利用服务器返回的数据的功能)提供给功能调用服务器。


[272]


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


请阅读此处了解ECMA(2016/17)中用于进行异步呼叫的新方法(@Felix Kling Answer on Top)
https://stackoverflow.com/a/14220323/7579856

其它参考14


2017回答:您现在可以在每个当前浏览器和节点

中完全按照自己的意愿行事

这很简单:



  • 回复承诺

  • 使用await,它将告诉JavaScript等待将其解析为vlue的承诺(如hTTP响应)

  • 将async关键字添加到父函数



这是你的代码的工作版本:[274] [275]


(async function(){

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

})()


所有当前浏览器和节点8都支持await [276]

其它参考15


另一种解决方案是通过顺序执行器nsynjs执行代码。[277]


如果基础功能被宣传



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. 浏览器承诺对象

  2. Q - JavaScript的承诺库

  3. A + Promises.js

  4. jQuery deferred

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



foo()成功中使用callback()功能。
试试这种方式。它简单易懂。 &NBSP; [278] [279] [280] [281] [282]


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

其它参考16


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

其它参考17


在与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),但它确实启动了一个执行在1s之后返回wohoo。但正如你所看到的,分配给bar的值是foo()中立即返回的东西,而不是后来的任何其他东西。


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


让我们问我们的功能 PROMISE
承诺实际上意味着它意味着:它意味着该功能可以保证您提供将来获得的任何输出。所以,让我们看看它在行动中解决我们上面的小问题:


function foo(){
   return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
    setTimeout ( function(){ 
      // promise is RESOLVED , when exececution reaches this line of code
       resolve('wohoo')// After 1 second, RESOLVE the promise with value 'wohoo'
    }, 1000 )
  })
}

let bar ; 
foo().then( res => {
 bar = res;
 console.log(bar) // will print 'wohoo'
});


因此,总结是 - 要处理异步函数,如基于ajax的调用等,您可以使用resolve值的承诺(您打算返回)。因此,简而言之,您在异步函数中解析值而不是返回

其它参考18


ECMAScript 6具有生成器,允许您以异步方式轻松编程。


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

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


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


const gen = myGenerator();
const asyncCallback = (...args) => gen.next(...args);
asyncCallback();


如果您需要定位不支持ES6的浏览器,您可以通过Babel或closure-compiler运行代码来生成ECMAScript 5。

其它参考19


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


function foo() {
    var result;

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

    return result;
}

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

其它参考20


问题是:



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



可以解释为:



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



解决方案是避免回调,并使用 Promises async/await 的组合。


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


(虽然它可以用Javascript编写,但我更喜欢用Python编写,并使用Transcrypt将其编译为Javascript。这很清楚。)[283]


让我们首先启用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")

其它参考21


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


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


考虑一个例子。你打电话给送奶工并点一些牛奶。当它到来时,你想把它放在你的咖啡里。你现在不能把牛奶放在你的咖啡里,因为它还没有在这里。你必须等到牛奶放入你的咖啡之前。换句话说,以下方法不会起作用:


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


输入promises



这是承诺概念的动机,承诺是一种特定类型的价值,代表某种未来异步结果。它可以代表已经发生的事情,或者将来会发生的事情,或者根本不会发生的事情。 Promise有一个名为then的方法,当promise表示的结果已经实现时,你传递一个动作。


在我们的牛奶和咖啡的情况下,我们设计order_milk返回到达牛奶的承诺,然后将put_in_coffee指定为then动作,如下所示:


order_milk() . then(put_in_coffee)


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


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


让我们对你的特定问题应用promises。我们将请求逻辑包装在一个函数中,该函数返回一个promise:


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


实际上,我们所做的只是$.ajax添加return来调用$.ajax。这是因为jQuery$.ajax已经返回了一种类似于承诺的东西。 (实际上,在没有详细说明的情况下,我们宁愿包装这个调用以便返回真正的承诺,或者使用$.ajax的替代方法。)现在,如果我们想要加载文件并等待它完成然后做一些事情,我们可以简单地说


get_data() . then(do_something)


例如,


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


当使用promises时,我们最终将许多函数传递给then,因此使用更紧凑的ES6样式箭头函数通常会有所帮助:


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


async关键字



但是,如果是同步的,必须以一种方式编写代码,如果异步则采用完全不同的方式,仍然存在一些模糊的不满。对于同步,我们写


a();
b();


但如果a是异步的,我们必须写下承诺


a() . then(b);


上面,我们说JS无法知道它需要等待第一次调用才能执行第二次。如果 某种方式告诉JS那会不会很好?事实证明有[[- await关键字,在一个特殊类型的函数中使用,称为async 功能。此功能是即将推出的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);
}

其它参考22


使用ES2017,你应该将它作为函数声明


async function foo() {
    var response = await $.ajax({url: '...'})
    return response;
}


像这样执行它。


(async function() {
    try {
        var result = await foo()
        console.log(result)
    } catch (e) {}
})()


或Promise语法


foo().then(response => {
    console.log(response)

}).catch(error => {
    console.log(error)

})

其它参考23


而不是向你抛出代码,有两个概念是理解JS如何处理回调和异步性的关键。 (那是一个字吗?)


事件循环和并发模型



你需要注意三件事; 队列;事件循环和堆栈 [284] [285]


在广泛,简单的术语中,事件循环就像项目管理器一样,它不断地监听任何想要在队列和堆栈之间运行和通信的函数。


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继续执行队列中的下一个项目,同时等待消息被添加回队列。


这就是为什么使用异步功能我们使用称为回调的东西。它有点像一个字面上的承诺。正如我承诺在某些时候返回某些东西 jQuery使用称为deffered.done deffered.faildeffered.always(以及其他)的特定回调。你可以在这里看到它们[286] [287]


所以你需要做的是传递一个承诺在某个时刻执行的函数,并传递给它的数据。


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


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


所以大部分时间(但并非总是)你会foo而不是foo()


希望这会有所帮助。当你遇到这样的事情似乎令人困惑时 - 我强烈建议你完全阅读文档,至少要了解它。它会让你成为一个更好的开发者。

其它参考24


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


这里有很多非常详细的信息,我不会重复任何一个。在JavaScript中编程的关键是首先要有整体执行的正确的心理模型



  1. 您的入口点是作为事件的结果执行的。对于
    例如,带有代码的脚本标记被加载到浏览器中。
    (因此,这就是为什么你可能需要关注的原因
    如果页面需要dom元素,则可以运行代码
    首先建造,等等。)

  2. 您的代码执行完毕 - 但是许多异步调用它
    make - 不执行任何的回调,包括XHR
    请求,设置超时,dom事件处理程序等。等待执行的每个回调都将位于一个队列中,等待轮到其他被触发的事件全部执行完毕后再运行。

  3. 每个单独回调XHR请求,设置超时或dom
    一旦被调用,该事件将运行完成。



好消息是,如果你理解这一点,你将永远不必担心竞争条件。首先,您应该如何组织代码本质上是对不同离散事件的响应,以及您希望如何将它们组合成逻辑序列。您可以使用promises或更高级别的新异步/等待作为工具,或者您可以自己动手。


但是你不应该使用任何战术工具来解决问题,直到你对实际的问题领域感到满意为止。绘制这些依赖关系的地图以了解什么时候需要运行。尝试对所有这些回调采用临时方法不是能为你服务。