Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/372.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 如何从异步调用返回响应?_Javascript_Jquery_Ajax_Asynchronous - Fatal编程技术网

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

Javascript 如何从异步调用返回响应?,javascript,jquery,ajax,asynchronous,Javascript,Jquery,Ajax,Asynchronous,我有一个函数foo,它发出一个异步请求。如何从foo返回响应/结果 我试图从回调中返回值,并将结果赋给函数中的一个局部变量,然后返回该局部变量,但这些方法实际上都不会返回响应,它们都返回未定义的响应或变量result的初始值 使用jQuery的ajax函数的示例: function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val},placeholder c = new XMLHttpRequest; c.

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

我试图从回调中返回值,并将结果赋给函数中的一个局部变量,然后返回该局部变量,但这些方法实际上都不会返回响应,它们都返回未定义的响应或变量result的初始值

使用jQuery的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.send(d||null)
}
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
→ 有关使用不同示例的异步行为的更一般性解释,请参见

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

问题 中的A代表。这意味着发送请求或接收响应将从正常执行流中删除。在您的示例中,$.ajax立即返回下一条语句return result;,在调用作为成功回调传递的函数之前执行

这里有一个类比,希望能使同步流和异步流之间的区别更清楚:

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

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

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+的承诺,如果您使用transpiler或regenerator,则在较旧的浏览器中可用 节点中流行的回调 如果您使用许多promise库中的一个,则可以在较旧的浏览器中使用promise和ES2015+ 在当前浏览器和node 7+中都可以使用这三种浏览器

ES2017+:承诺与 2017年发布的ECMAScript版本引入了对异步函数的语法级别支持。在async和await的帮助下,您可以以同步样式编写异步。代码仍然是异步的,但更易于阅读/理解

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();
异步/等待构建在承诺之上:异步函数总是返回承诺。Wait打开一个承诺,要么导致该承诺的解析值,要么在承诺被拒绝时抛出错误

重要提示:您只能在异步函数中使用wait。目前,还不支持顶级wait,因此您可能必须创建一个异步iLife来启动异步上下文

您可以阅读有关MDN的更多信息

下面是一个详细说明上述延迟函数findItem的示例:

当前和版本支持异步/等待。您还可以通过使用regenerator或使用regenerator的工具(如)将代码转换为ES5来支持较旧的环境

让函数接受回调 回调是指将函数1传递给函数2。函数2可以随时调用函数1。在异步进程的上下文中,只要异步进程完成,就会调用回调。通常,结果会传递给回调

function callServerAsync(){
    $.ajax({
        url: '...',
        success: function(response) {

            successCallback(response);
        }
    });
}

function successCallback(responseObj){
    // Do something like read the response and show data
    alert(JSON.stringify(responseObj)); // Only applicable to a JSON response
}

function foo(callback) {

    $.ajax({
        url: '...',
        success: function(response) {
           return callback(null, response);
        }
    });
}

var result = foo(function(err, result){
          if (!err)
           console.log(result);
});
在问题的示例中,您可以让foo接受回调并将其用作成功回调。那么这个

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);
福伊特尔 f的定义如下:

function foo(callback) {
    $.ajax({
        // ...
        success: callback
    });
}
order_milk() . then(put_in_coffee)
回调将引用调用foo时传递给foo的函数,并将其传递给success。也就是说,一旦Ajax请求成功,$.Ajax将调用callback并将响应传递给回调,而回调可以通过result引用,因为我们就是这样定义回调的

function callServerAsync(){
    $.ajax({
        url: '...',
        success: function(response) {

            successCallback(response);
        }
    });
}

function successCallback(responseObj){
    // Do something like read the response and show data
    alert(JSON.stringify(responseObj)); // Only applicable to a JSON response
}

function foo(callback) {

    $.ajax({
        url: '...',
        success: function(response) {
           return callback(null, response);
        }
    });
}

var result = foo(function(err, result){
          if (!err)
           console.log(result);
});
您还可以在将响应传递给回调之前对其进行处理:

function foo(callback) {
    $.ajax({
        // ...
        success: function(response) {
            // For example, filter the response
            callback(filtered_response);
        }
    });
}
使用回调编写代码比看起来更容易。毕竟,浏览器中的JavaScript在很大程度上是事件驱动的DOM事件。接收Ajax响应只是一个事件。 当您必须使用第三方代码时,可能会出现困难,但大多数问题都可以通过思考应用程序流程来解决

ES2015+:承诺与 这是ECMAScript 6 ES2015的一个新功能,但它已经有了很好的表现。还有许多库实现了标准Promises API,并提供了其他方法来简化异步函数的使用和组合,例如

承诺是未来价值的容器。当承诺接收到已解析的值或被取消或拒绝时,它会通知所有希望访问此值的侦听器

与普通回调相比,它们的优点是允许您将代码解耦,并且更易于编写

以下是使用承诺的示例:

function get_data() {
  return $.ajax('/foo.json');
}
功能延迟{ //'delay'返回一个承诺 返回新的允诺函数解析、拒绝{ //只有“延迟”才能解决或拒绝承诺 setTimeoutfunction{ resolve42;//3秒后,解析值为42的承诺 }, 3000; }; } 延迟 .thenfunctionv{/`delay`返回一个承诺 console.logv;//解析后记录值 } .catchfunctionv{ //如果被拒绝,也可以做其他事情 //在本例中不会发生这种情况,因为未调用'reject'。 }; .as控制台包装器{max height:100%!important;top:0;}如果您的代码中没有使用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'
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.
为使用jqueryforajax的人编写一个答案,但我决定为不使用jqueryforajax的人提供一个替代方案

你面对的是什么 这是另一个答案对问题解释的简短摘要,如果你在阅读此答案后不确定,请阅读此答案

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

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

这里有一个简单的类比:

function getFive(){
    var a;
    setTimeout(function(){
         a=5;
    },10);
    return a;
}
返回的值未定义,因为a=5部分尚未执行。AJAX的行为是这样的,您在服务器有机会告诉您的浏览器该值是什么之前返回该值

这个问题的一个可能的解决方案是重新编码,告诉您的程序在计算完成后要做什么

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

function getFive(whenDone){
    var a;
    setTimeout(function(){
         a=5;
         whenDone(a);
    },10);
}
这就是所谓的。基本上,我们向getFive传递一个在事件完成时要执行的操作,告诉代码在事件完成时如何反应,比如AJAX调用,或者在本例中是超时

用途如下:

getFive(onComplete);
应向屏幕发出警报5

可能的解决方案 基本上有两种解决方法:

使AJAX调用同步—我们称之为SJAX。 重新构造代码以正确处理回调。 1.同步AJAX-不要这样做!! 至于同步AJAX,不要这样做!费利克斯的回答提出了一些令人信服的论点,说明为什么这是个坏主意。总之,它将冻结用户的浏览器,直到服务器返回响应并创建非常糟糕的用户体验。以下是MDN关于原因的另一个简短总结:

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


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

如果必须这样做,您可以传递一个标志

2.重组代码 让函数接受回调。在示例代码中,可以使foo接受回调。我们将告诉我们的代码在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(function(result) {
    // Code that depends on `result`
});
在这里,我们传递了一个匿名函数,但我们可以同样轻松地传递对现有函数的引用,使其看起来像:

function myHandler(result) {
    // Code that depends on `result`
}
foo(myHandler);
有关如何完成这种回调设计的更多详细信息,请查看Felix的答案

现在,让我们定义foo本身来相应地执行操作

function foo(callback) {
    var httpRequest = new XMLHttpRequest();
    httpRequest.onload = function(){ // When the request is loaded
       callback(httpRequest.responseText);// We're calling our method
    };
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
}
现在,我们已经让我们的foo函数接受一个操作,以便在AJAX成功完成时运行。我们可以通过检查响应 nse状态不是200,并相应地创建失败处理程序等。它有效地解决了我们的问题

如果你仍然很难理解这一点,那么在MDN。

2首先,阅读和的答案

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

它比列出的所有其他函数都要短。 回调是直接设置的,因此没有额外的不必要的闭包。 它使用新的onload,因此您不必检查readystate&&status 还有其他一些情况,我不记得了,这让XMLHttpRequest1很烦人。 有两种方法可以使用XMLHttpRequest变量名获取此Ajax调用的响应:

最简单的:

this.response
或者如果出于某种原因将回调绑定到类:

e.target.response
例如:

function callback(e){
  console.log(this.response);
}
ajax('URL', callback);
或者上面的一个更好匿名函数始终是一个问题:

ajax('URL', function(e){console.log(this.response)});
没有比这更容易的了

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

退房

它支持所有*现代浏览器。我可以确认,自从创建XMLHttpRequest2以来,我一直在使用这种方法。我使用的任何浏览器都没有任何问题

onreadystatechange仅在希望获取状态2上的标题时有用

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

现在,如果您想使用和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)
}
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
再次。。。这是一个很短的函数,但它可以完成并发布

用法示例:

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
或传递完整的表单元素文档。getElementsByTagName“表单”[0]:

或设置一些自定义值:

var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);
如您所见,我没有实现同步。。。这是件坏事

话虽如此。。。我们为什么不用简单的方法呢

如评论中所述,使用error&&synchronous确实完全打破了答案的要点。哪种方法是正确使用Ajax的好方法

错误处理程序

在上面的脚本中,您有一个静态定义的错误处理程序,因此它不会影响函数。错误处理程序也可以用于其他函数

但要想真正找出一个错误,唯一的办法就是写一个错误的URL,在这种情况下,每个浏览器都会抛出一个错误

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

即使您将“postpapap”作为方法传递,它也不会抛出错误

即使将“fdggdgildfdghfldj”作为formdata传递,它也不会抛出错误

在第一种情况下,错误位于displayAjax中this.statusText as方法下,不允许

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

不允许跨域自动抛出错误

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

只有设置为错误的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');
没有其他方法可以以非异步方式实现这一点。是的,设置超时循环。。。但说真的

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

仅当您有一个页面,在该页面中,您总是加载相同的XML/JSON或任何只需要一个函数的内容时。在这种情况下,稍微修改Ajax函数并用您的特殊函数替换b

以上功能仅供基本使用

如果你想扩展这个函数

是的,你可以

我使用了很多API,我集成到每个HTML页面中的第一个函数之一就是这个答案中的第一个Ajax函数,带有GET only

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

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

但是在这些情况下,您应该只为该pur创建一个函数 姿势有时候你需要一个blob,数组缓冲区,你可以设置头,覆盖mimetype,还有很多


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

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

function callServerAsync(){
    $.ajax({
        url: '...',
        success: function(response) {

            successCallback(response);
        }
    });
}

function successCallback(responseObj){
    // Do something like read the response and show data
    alert(JSON.stringify(responseObj)); // Only applicable to a JSON response
}

function foo(callback) {

    $.ajax({
        url: '...',
        success: function(response) {
           return callback(null, response);
        }
    });
}

var result = foo(function(err, result){
          if (!err)
           console.log(result);
});

您错误地使用了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);
    }
});
在提交处理程序中返回任何内容都不会起任何作用。相反,您必须交出数据,或者直接在success函数中处理数据。

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

上面说,

承诺可用于取消异步函数的测试,并允许将多个函数链接在一起

你也可以找到一个很好的解释

下面提到了一个例子

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

 // promiseB will be resolved immediately after promiseA is resolved
 // and its value will be the result of promiseA incremented by 1.
Angular 2及更高版本 在Angular 2中,请看下面的示例,但使用Angular 2的可观测值是必要的

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

search() {
    this.searchService.search(this.searchField.value)
      .then((result) => {
    this.result = result.artists.items;
  })
  .catch((error) => console.error(error));
}
请看这里的帖子。但TypeScript不支持,如果您想使用它,可能需要插件

此外,这里还有一个问题。

如果你在使用承诺,这个答案是为你准备的。 这意味着AngularJS、带延迟的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'
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.
为使用jQuery和Ajax回调的用户编写答案。我有一个关于原生XHR的答案。这个答案适用于前端或后端承诺的一般用法

核心问题 浏览器和带有Node.js/io.js的服务器上的JavaScript并发模型是异步的和反应式的

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

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

这里有一个简单的类比:

函数getFive{ var数据; setTimeoutfunction{//在将来设置一秒钟的计时器 data=5;//一秒钟后,执行此操作 }, 1000; 返回数据; }
document.body.innerHTML=getFive;//`undefined`here not 5从异步函数返回值的另一种方法是传入一个将存储异步函数结果的对象

下面是一个相同的示例:

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

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

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

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

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

举个例子。你打电话给送牛奶的人点了些牛奶。当它来的时候,你想把它放在你的咖啡里。你现在不能把牛奶放进你的咖啡里,因为它还没到。你得等它来了再把它放进咖啡里。换言之,以下方法不起作用:

var milk = order_milk();
put_in_coffee(milk);
因为JavaScript无法知道它需要等待order_milk完成后才能执行put_in_coffee。换句话说,它不知道order_milk是异步的,直到将来某个时候才会产生milk。JavaScript和其他声明性语言在不等待的情况下执行一个又一个语句

中情局 解决这个问题的ssic-JavaScript方法利用JavaScript支持函数作为可以传递的一级对象的事实,将函数作为参数传递给异步请求,然后在将来某个时候完成任务时调用。这就是回调方法。看起来是这样的:

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

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

    foo(); // The call happens here
});
点牛奶开始,点牛奶,然后,只有当牛奶到达时,它才会调用put_in_coffee

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

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

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

    foo(); // The call happens here
});
在我经过的地方,我要把牛奶放进咖啡里,也要把牛奶放进咖啡里后,我要执行的动作就是喝咖啡。这样的代码变得难以编写、读取和调试

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

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

function callback(data) {
  console.log(data);
}
承诺 这就是承诺概念的动机,承诺是一种特殊类型的价值,代表某种未来或异步结果。它可以表示已经发生的事情,或者将来将要发生的事情,或者根本不可能发生的事情。承诺有一个名为then的方法,当承诺所代表的结果实现时,您可以将要执行的操作传递给该方法

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

function foo(callback) {
    $.ajax({
        // ...
        success: callback
    });
}
order_milk() . then(put_in_coffee)
这样做的一个优点是,我们可以将它们串在一起,以创建未来事件的序列链接:

order_milk() . then(put_in_coffee) . then(drink_coffee)
让我们对你的特殊问题做出承诺。我们将请求逻辑封装在一个函数中,该函数返回一个承诺:

function get_data() {
  return $.ajax('/foo.json');
}
实际上,我们所做的只是在调用$.ajax时添加了一个返回。这是因为jQuery的$.ajax已经返回了一种类似于承诺的东西。在实践中,在不深入细节的情况下,我们更愿意包装这个调用,以便返回一个真正的承诺,或者使用$.ajax之外的其他方法。现在,如果我们想加载文件并等待它完成,然后做一些事情,我们可以简单地说

get_data() . then(do_something)
比如说,

get_data() .
  then(function(data) { console.log(data); });
在使用Promission时,我们最终会将大量函数传递给then,因此使用更紧凑的ES6风格的箭头函数通常会有所帮助:

get_data() .
  then(data => console.log(data));
async关键字 但对于同步时必须以一种方式编写代码,异步时必须以另一种方式编写代码,仍然有一些模糊的不满意之处。对于同步,我们编写

a();
b();
但是如果a是异步的,那么我们必须编写承诺

a() . then(b);
如上所述,JavaScript无法知道它需要等待第一个调用完成,然后才能执行第二个调用。如果有办法告诉JavaScript,那不是很好吗?事实证明,在一种称为异步函数的特殊类型的函数中使用了wait关键字。此功能是即将发布的ECMAScript ES版本的一部分,但它已经在Transpiler中可用,例如给定正确的预设。这使我们可以简单地写

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

虽然承诺和回访在许多情况下都能很好地工作,但表达以下内容会让人感到痛苦:

if (!name) {
  name = async1();
}
async2(name);
您最终将经历async1;检查名称是否未定义,并相应地调用回调

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

async1(name, async2)
虽然在小示例中这是可以的,但是当您有很多类似的案例和错误处理时,它会变得很烦人

纤维有助于解决这个问题

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调用的响应; 使用模式; 使用模式连接多个HTTP调用; 这个工作示例是自包含的。它将定义一个简单的请求对象,该对象使用窗口XMLHttpRequest对象进行调用。它将定义一个简单的函数来等待一系列承诺的完成

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

function getFive(whenDone){
    var a;
    setTimeout(function(){
         a=5;
         whenDone(a);
    },10);
}
上下文。该示例是查询端点以搜索给定查询字符串集的播放列表对象:

[
 "search?type=playlist&q=%22doom%20metal%22",
 "search?type=playlist&q=Adele"
]
对于每个项目,一个新的承诺将触发一个block-ExecutionBlock,解析结果,根据结果数组(即Spotify用户对象的列表)安排一组新的承诺,并在ExecutionProfileBlock内异步执行新的HTTP调用

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

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

-H "Authorization: Bearer {your access token}" 
因此,要运行以下示例,需要将访问令牌放入请求头中:

-H "Authorization: Bearer {your access token}" 
var spotifyAccessToken=您的spotifyAccessToken; 变量控制台={ 日志:函数{ document.getElementByIdconsole.innerHTML+=s+ } } //简单XMLHttpRequest //基于https://davidwalsh.name/xmlhttprequest SimpleRequest={ 呼叫:什么,应答{ var请求; 如果window.XMLHttpRequest{//Mozilla、Safari。。。 请求=新的XMLHttpRequest; }如果window.ActiveXObject{//Internet Explorer 试一试{ request=newActiveXObject'Msxml2.XMLHTTP'; } 抓住e{ 试一试{ 请求=新的ActiveXObject'Microsoft.XMLHTTP'; }捕获e{} } } //状态变化 request.onreadystatechange=函数{ 如果request.readyState==4{//Done 如果request.status==200{//完成 responserequest.responseText } 其他的 回答 } } 请求。打开“获取”,什么,真; request.setRequestHeaderAuthorization,承载者+spotifyAccessToken; request.sendnull; } } //允诺者 var promiseAll=functionitems,block,done,fail{ var self=这个; var承诺=[], 指数=0; items.forEachfunctionitem{ 承诺,我{ 返回新的允诺函数解析、拒绝{ if块{ block.applythis[项目、索引、解决、拒绝]; } }; }项目++索引 }; Promise.allpromises.then函数AcceptHandlerresults{ 如果完成,则返回结果; },函数错误HandlerError{ 如果失败或错误; }; }; //允诺者 //延迟执行块 var ExecutionBlock=函数项、索引、解析、拒绝{ 变量url=https://api.spotify.com/v1/ url+=项目; console.log url SimpleRequest.callurl,functionresult{ 如果结果{ var profileUrls=JSON.parseresult.playlists.items.mapfunctionitem,索引{ return item.owner.href; } 解析URL; } 否则{ 拒绝新的错误呼叫错误; } } } arr=[ 搜索?类型=播放列表&q=%22doom%20metal%22, 搜索?类型=播放列表&q=阿黛尔 ] promiseAllarr、functionitem、索引、解析、拒绝{ console.logMaking请求[+index+] ExecutionBlockitem、索引、解析、拒绝; },functionresults{//聚合结果 console.logAll profiles received+results.length; //console.logJSON.stringifyresults[0],null,2; /////再次承诺 var ExecutionProfileBlock=函数项、索引、解析、拒绝{ SimpleRequest.callitem、functionresult{ 如果结果{ var obj=JSON.parseresult; 决心{ 名称:obj.display_name, 追随者:obj.followers.total, url:obj.href }; }//结果 } }//ExecutionProfileBlock promiseAllresults[0],functionitem,index,resolve,reject{ //console.logMaking请求[+索引+]+项 ExecutionProfileBlockitem、索引、解析、拒绝; },functionresults{//聚合结果 console.logAll接收到的响应+results.length; console.logJSON.stringifyresults,null,2; } ,functionerror{//Error console.logerror; } ///// }, 函数错误{//Error console.logerror; }; 简而言之,您必须实现如下回调:

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

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

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

您可以使用使用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);
        };
    });
}
看看这个例子:

正如您所看到的,getJoke返回的是已解析的承诺,它在返回res.data.value时已解析。因此,您需要等待$http.get请求完成,然后作为正常的异步流执行console.logres.joke

这是plnkr:

ES6方式异步-等待


我会用一幅可怕的手绘漫画来回答。第二幅图是代码示例中未定义结果的原因

以下是处理异步请求的一些方法: -JavaScript的promise库 在第一个答案中使用回调概念作为实现 示例:jQuery延迟实现以处理多个请求 var-App=App |{}; 应用={ getDataFromServer:函数{ var self=这个, 递延=$。递延, 请求=[]; push$.getJSON'request/ajax/url/1'; push$.getJSON'request/ajax/url/2'; $.when.applyjQuery,requests.donefunctionxhrResponse{ 返回deferred.resolvexhrResponse.result; }; 回程延迟 D }, init:函数{ 这是.getDataFromServer.done\u.bindfunctionresp1,resp2{ //当您需要时,执行您想要执行的操作 //从Ajax获取响应,例如日志响应。 }这,; } }; App.init 在foo success中使用回调函数。 这样试试看。它简单易懂

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

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

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

function callback(e){
  console.log(this.response);
}
ajax('URL', callback);
//错 var theArray=[1,2,3]; var结果=[]; 数组。forEachfunctionentry{ doSomethingAsyncentry,functionresult{ 结果。结果; }; }; 控制台。日志结果:,结果;//例如,使用、归还等。 函数doSomethingAsyncvalue,回调{ console.log为+值启动异步操作; setTimeoutfunction{ console.log完成+值的异步操作; 回调值*2; },Math.floorMath.random*200; }
.作为控制台包装器{max height:100%!important;}这是许多新JavaScript框架中使用的双向数据绑定或存储概念将非常适合您的地方之一

因此,如果您正在使用,或任何其他执行双向数据绑定或存储概念的框架,那么这个问题对您来说是简单地修复的,因此简单地说,您的结果在第一阶段是未定义的,因此您在收到数据之前得到了result=undefined,然后一旦得到结果,它将被更新,并被分配到新的值,该值是Ajax调用的响应

但您如何在纯JavaScript或jQuery中实现这一点,例如,正如您在这个问题中所问的那样

您可以使用回调、承诺和最近可观察来为您处理它。例如,在promises中,我们有一些函数,如success或then,当您的数据准备就绪时将执行这些函数。对于可观察对象上的回调函数或订阅函数也是如此

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

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

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

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

有关更多信息,请研究承诺和可观察性,它们是实现这种异步功能的较新方法。

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

如果基础功能得到了保证 nsynjs将按顺序评估所有承诺,并将承诺结果放入数据属性:

函数同步码{ var getURL=functionurl{ 返回window.fetchurl.data.text.data; }; var url='1〕https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js'; log'received bytes:',getURLurl.length; }; nsynjs.runsynchronousCode,{},函数{ console.log'synchronousCode done'; }; 2017年答案:您现在可以在当前的每个浏览器和 这很简单:

还愿 使用,这将告诉JavaScript等待承诺被解析为类似HTTP响应的值 将关键字添加到父函数 以下是您的代码的工作版本:

(async function(){

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

})()

当然有很多方法,比如同步请求、承诺,但根据我的经验,我认为您应该使用回调方法。JavaScript的异步行为是很自然的

因此,您的代码片段可以重写为稍有不同:

function foo() {
    var result;

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

    return result;
}

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

这是我们在与JavaScript的“神秘”作斗争时经常遇到的问题。今天让我试着解开这个谜

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

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)
}
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
这是一个简单的同步函数调用,其中每一行代码在下一行之前“完成其任务”,结果与预期的相同

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

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)
}
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在执行时不返回任何内容,因此返回的值是未定义的,但它会启动计时器,计时器在1秒后执行函数以返回“wohoo”。但正如您所看到的,分配给bar的值是foo立即返回的内容,它是nothing,即未定义的

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

让我们向函数请求一个承诺。 Promise实际上是关于它的含义:它意味着函数保证您提供将来得到的任何输出。让我们看看上面的小问题:

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

let bar;
foo().then( res => {
    bar = res;
    console.log(bar) // Will print 'wohoo'
});
因此,总结是——为了处理异步函数,如基于Ajax的调用等,您可以使用承诺来解析要返回的值。因此,简而言之,您解析值,而不是返回值 同步功能

使用异步/等待更新承诺 除了使用then/catch来处理承诺之外,还有一种方法。其思想是识别异步函数,然后等待承诺得到解决,然后再转到下一行代码。它仍然只是在兜帽下的承诺,但有一个不同的句法方法。为了让事情更清楚,您可以在下面找到一个比较:

然后/捕获版本: 异步/等待版本: 问题是:

如何从异步调用返回响应

这可以解释为:

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

解决方案是避免回调,并结合使用承诺和异步/等待

我想给出一个Ajax请求的示例

findItem(function(item) {
    // Do something with the item
});
doSomethingElse();
虽然它可以用JavaScript编写,但我更喜欢用Python编写,并使用JavaScript将其编译为JavaScript。这就足够清楚了

让我们首先启用jQuery用法,使$s可用:

定义一个返回承诺的函数,在本例中为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()
将异步代码当作同步代码使用:

JavaScript是单线程的

浏览器可分为三个部分:

事件循环

Web API

事件队列

事件循环将永远运行,即一种无限循环。事件队列是在某个事件上推送所有函数的位置,例如:单击

这是一个接一个地从队列中执行并放入事件循环中,事件循环执行此函数,并在第一个函数执行后为下一个函数做好准备。这意味着只有在事件循环中执行队列中某个函数之前的函数,才会开始执行该函数

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

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

队列中的下一个函数是循环中的数据,但由于没有可用的数据,它将被浪费,下一个函数的执行将持续到队列结束。这被称为异步调用,也就是说,在获得数据之前,我们可以做其他事情

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

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

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

来自的图像正确解释了回调使用* 我们使用从服务器返回给调用服务器的函数的数据来提供函数

在我的代码中,它被称为:

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

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或closure编译器运行代码来生成ECMAScript 5

callback…arg被包装在一个数组中,并在读取它们时被解构,以便模式能够处理具有多个参数的回调。例如:


有两个概念是理解JavaScript如何处理回调和异步的关键,而不是向您抛出代码,这是一个词吗

有三件事你需要注意;排队;和堆栈

广义地说,简单地说,事件循环就像项目管理器一样,它不断地侦听任何想要运行的函数,并在队列和堆栈之间进行通信

while (queue.waitForMessage()) {
  queue.processNextMessage();
}
一旦接收到要运行某个东西的消息,它就会将其添加到队列中。队列是等待执行的事情的列表,比如AJAX请求。想象一下:

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

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

    foo(); // The call happens here
});
使用foobarFunc调用foo.com/api/bar 去执行一个无限循环 ... 等等 当其中一条消息要执行时,它会从队列中弹出消息并创建一个堆栈,该堆栈就是JavaScript执行消息中的指令所需执行的所有内容。所以在我们的示例中,它被告知调用foobarFunc

因此,在本例中,foobarFunc需要执行的任何操作都会被推送到堆栈上。执行,然后忘记-然后事件循环将移动到队列中的下一个事件或侦听消息

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

什么时候开始 例如,当您使用AJAX调用外部方或运行任何异步代码setTimeout时,JavaScript依赖于响应才能继续

最大的问题是它什么时候能得到回应?答案是我们不知道-所以事件循环正在等待消息说hey run me。如果JavaScript只是同步地等待那个消息,你的应用程序就会冻结,它会很糟糕。因此JavaScript在等待消息添加回队列的同时继续执行队列中的下一项

这就是为什么对于异步功能,我们使用称为回调的东西一种函数或处理程序,当传递到另一个函数时,将在以后执行。A使用传递给的回调函数。然后,例如,作为一种以更线性的方式对这种异步行为进行推理的方法。承诺是一种表示我承诺在某个时刻返回某些东西的方式,回调是我们处理最终返回的值的方式。jQuery使用名为deffered.done deffered.fail和deffered.always的特定回调。你都可以看到

因此,您需要做的是传递一个函数,该函数承诺在某个点上使用传递给它的数据执行

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

function foo(bla) {
  console.log(bla)
}
所以大多数时候,但并非总是你会通过foo而不是foo

希望这会有一些意义。当你遇到类似这样的事情时,我强烈建议你充分阅读文档,至少对它有一个了解。它将使您成为一名更好的开发人员。

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

用法 但是等等。。。! 使用承诺有问题

我们为什么要使用自己的定制承诺? 我使用这个解决方案有一段时间了,直到我发现旧浏览器中有一个错误:

未捕获引用错误:未定义承诺

因此,我决定为ES3实现我自己的Promise类,如果它没有定义的话,则将其应用到下面的JavaScript编译器中。只需在主代码之前添加此代码,然后安全地使用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;
    }();
}

在阅读了这里的所有回复并结合我的经验之后,我想继续讲述JavaScript异步编程中回调、承诺和异步/等待的细节

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 — 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函数为我们提供了一种简洁明了的语法,使我们能够编写更少的代码,以实现与承诺相同的结果。Async/Await看起来类似于同步代码,而且同步代码更易于读写。为了用Async/Await捕捉错误,我们可以使用块try…catch。在这里,您不需要编写一连串的.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 for 主要针对XHR请求重构异步代码


@Pommy:如果你想使用jQuery,你必须包括它。请参阅。在解决方案1的子jQuery中,我无法理解这一行:如果您使用任何其他jQueryAjax方法,如$.get、$.getJSON等,那么就必须将它们转换为$.AJAX。是的,我意识到我的尼克在这方面有点讽刺case@gibberish:嗯,我不知道怎样才能说得更清楚。您是否看到如何调用foo并将函数传递给它foofunctionresult{….};?结果在这个函数中使用,是Ajax请求的响应。为了引用此函数,foo的第一个参数被称为callback,并被分配给success,而不是匿名函数。因此,$.ajax将在请求成功时调用回调。我试着再解释一下。这个问题的聊天已经结束了,所以我不确定该在哪里提出大致的更改,但我建议:1将同步部分改为一个简单的讨论,讨论为什么它不好,没有代码示例说明如何做。2删除/合并回调示例,只显示更灵活的延迟方法,我认为对于学习Java的人来说,这可能更容易遵循

杰西:我想你误解了答案的这一部分。如果希望Ajax请求是同步的,则不能使用$.getJSON。但是,您不应该希望事件请求是同步的,因此这不适用。您应该使用回调或承诺来处理响应,正如前面的回答中所解释的。同步请求会阻止代码的执行,并会泄漏内存和事件。同步请求如何泄漏内存?@MatthewG我在中添加了一个悬赏,我将看看我能找到什么。同时,我将从答案中删除引号。仅供参考,XHR 2允许我们使用onload处理程序,它仅在readyState为4时触发。当然,IE8不支持它。iirc,可能需要确认。您关于如何将匿名函数作为回调传递的解释是有效的,但会产生误导。示例var bar=foo;请求定义一个变量,而您建议的foofuncim{};没有定义barI可以理解为什么这可能有点误导,但是在几个月不知道这个问题的答案之后,我终于明白了发生了什么,以及如何避免它。你只想让你的异步函数不使用任何公共变量,并用它们自己的私有变量将它们的返回数据传递给函数。我不知道为什么这个答案会让我找到这个/我的解决方案,但它确实做到了。谢谢你,伙计!我也意识到这个问题可能不是问我在描述什么,但是它至少是相关的/相似的。虽然这个答案很好,我们都喜欢XHR2,发布文件数据和多部分数据非常棒-这显示了用JavaScript发布XHR的语法优势-你可能想把它放在一篇博客文章中,我喜欢它,甚至放在一个库中,不确定名称x,ajax或XHR可能更好:。我不知道它如何处理从AJAX调用返回的响应。有些人仍然可以执行var res=xurl,却不明白它为什么不起作用;。另一方面,如果您从方法中返回c,这样用户就可以钩住错误等,这将是很酷的。2.ajax是异步的。。所以没有var res=x'url'。。这就是这个问题和答案的全部要点:如果在第一行你覆盖了它的任何值,为什么函数中会有一个“c”参数?我遗漏了什么吗?您可以使用参数作为占位符,以避免多次写入var@cocco所以你在一个简单的答案中写了误导性的、不可读的代码,以避免几次击键?请不要那样做。我不知道是谁投了反对票。但这是一个行之有效的方法,事实上我用这种方法创建了一个完整的应用程序。ajax不返回数据,因此最好使用上述方法。如果它是错误的,那么请解释并建议更好的方法。对不起,我忘了留下一条我通常会做的评论!。我投了反对票。否决票并不表示事实的正确性或缺乏,它们表示在上下文中有用或缺乏。我不认为你的答案有用,因为费利克斯已经详细解释了这一点。另一方面,如果响应是JSON,为什么要对其进行字符串化呢@Benjamin我使用stringify将JSON对象转换为字符串。谢谢你澄清你的观点。将记住发布更详细的答案。如果您想在successCallback之外返回responseObj,该怎么办…:。。。你打算怎么做。。。因为一个简单的返回将返回到ajax的成功回调。。。而不是在successCallback之外……这根本不能解释承诺如何解决这个问题。jQuery和方法都会返回承诺。我建议修改你的答案。虽然jQuery的和现在的不太一样,但是catch不是。在这里使用对象没有什么特别的。如果您将he响应直接指定给结果,也可以这样做。它之所以有效,是因为您在异步函数完成后读取变量。虽然它有效,但实际上并不比分配给全局变量更好。这个答案完全是语义上的。。。成功方法只是回调中的回调。你可以成功:handleData,它会起作用。如果你想返回handleData之外的responseData,该怎么办。。。你打算怎么做。。。因为一个简单的返回将返回到ajax的成功回调。。。而不是在handleData之外…@Jacques和pesho hristov你错过了这一点。提交处理程序不是成功方法,它是$.ajax的周围范围。@travnik我没有错过这一点。如果你把handleData的内容放在成功方法中,它的行为将完全相同…这个答案是如此清晰和简单,节省了我的时间!。非常感谢。如果可以的话,我会投票100次。一张图片抵得上千言万语,人员A-询问人员B的详细信息以修复他的汽车,反过来人员B-进行Ajax调用并等待服务器对汽车修复详细信息的响应,当收到响应时,Ajax成功函数调用人员B函数并

将响应作为参数传递给它,人员A将收到答案。如果您在每个图像中添加代码行来说明概念,那将非常好。同时,带车的人被卡在路边。他要求在继续之前把汽车修好。他现在独自一人在路边等待。。。他宁愿在电话上等待状态的改变,但机修工不会这么做。。。机修工说他必须继续工作,不能只是打电话。机修工答应他会尽快给他回电话。大约4小时后,这家伙放弃了,打电话给优步超时的例子。@barrypicker:-D太棒了@FingLixon无论如何它都不是一部完美的喜剧:-D。第二幅图应该说明当你在回调发生之前过早读取一个值时会发生什么。第三幅图演示了如何设置回调方法:左边的人基本上就是回调处理程序:一旦信息可用,他就会被调用,然后可以随心所欲地使用它。我现在觉得在这部漫画里有两个电话是个坏主意:一个打到商店,一个打到左边那个人。我应该简化它,很抱歉。@recurf-这不是我的项目。您可以尝试使用他们的问题跟踪器。这与生成器功能类似吗*这仍然相关吗?如果您使用的是node的一些最新版本,那么可以使用async Wait。如果有人坚持使用旧版本,他们可以使用这种方法。你能解释一下代码的If-expecting==0部分是如何工作的吗?您的解决方案的回调版本对我来说非常有效,我只是不明白您是如何通过该语句检查已完成的响应数的。感谢你,我只是缺乏知识。有没有其他方法可以写入检查?@Sarah:expecting从array.length的值开始,这是我们要发出的请求数。我们知道在所有这些请求启动之前不会调用回调。在回调中,if-expecting==0执行以下操作:1。减量期望我们收到了响应,因此我们期望少一个响应,如果减量后的值为0,我们不期望有更多响应,我们就完成了@Henke-我认为这确实是个人的偏好,通常我更喜欢记录原始数据,让控制台处理,在这种情况下,我认为您的更改是正确的。谢谢-为了方便我和其他人,添加了一个指向相关答案的链接:。不幸的是,这只适用于返回承诺的函数——例如,它不适用于使用回调的Node.js API。我不建议在没有Babel的情况下使用它,因为并不是每个人都使用当前的浏览器。@MichałPerłakowski node 8包含可用于做出node.js API返回承诺的内容。你是否有时间和金钱支持非当前浏览器显然取决于你的情况。IE 11在2018年仍然是当前浏览器,遗憾的是,它不支持Wait/asyncIE11不是当前浏览器。它是5年前发布的,根据caniuse的数据,它的全球市场份额为2.5%,除非有人把你的预算翻了一番而忽略了所有当前的技术,否则它不值得大多数人花费时间。这很有趣。我喜欢它允许用其他语言编写异步调用代码的方式。但从技术上讲,它不是真正的JavaScript?这在全局范围内很好,但在某些模块上下文中,您可能希望确保回调的上下文正确,例如$.ajax{url:api/data,success:fooDone.bindthis};这实际上是不正确的,因为React是单向数据binding@MatthewBrent您没有错,但也没有错,React道具是对象,如果更改,它们会在整个应用程序中更改,但React开发者不建议使用它……回调或JavaScript没有本质上的异步性。为什么要保留var result;并返回结果;?后者仍将始终返回未定义!这仍然被认为是从承诺或异步/等待返回值的最佳方式吗?@edwardsmarkf个人认为没有这样的最佳方式。我将承诺与then/catch、async/await以及代码异步部分的生成器一起使用。这在很大程度上取决于使用的上下文。这应该是公认的答案+1表示async/await,但我们是否应该返回await data.json;?这么好的解释。。谢谢SRHI,虽然这个答案的内容是准确的,但是它并不能回答OP的问题,那就是如何从异步调用中返回一些东西?你认为发电机/异步发电机仅仅是一个异步API解决方案吗?或者使用生成器包装另一个异步API,如promise/deffered?我同意这是对异步世界的另一个有力补充,但仍然没有找到合适的生成器使用方法来让我采用它们。我很难接受回调有点像承诺。就像说
面粉有点像面包,但事实并非如此。你使用面粉、水和其他添加剂,混合它们,经过一个过程,最终得到的结果是面包。这是真的——我想我是想说一些不太明白我意思的话。JS中的承诺显然代表与回调不同的东西,然而,当编程任何类型的异步功能时,您将执行回调。承诺代表价值,但回调是我们需要在将来某个时候,当它返回时,使用该价值做一些事情。承诺基本上是无用的,但并不总是没有回调来使用解析的价值做一些事情。还要添加与承诺相关的信息为什么要包含输出错误的堆栈片段?任何对使用async/await感兴趣的人都可能希望阅读下面的注释:-。