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

时间:2013-01-08 17:06:14

标签: javascript ajax asynchronous xmlhttprequest event-loop

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

42 个答案:

答案 0 :(得分:5237)

  

→有关不同示例的异步行为的更一般说明,请参阅 Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference

     

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

问题

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

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

同步

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

当您进行包含&#34;正常&#34;的函数调用时,会发生同样的情况。代码:

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的异步特性!虽然某些异步操作提供了同步对应物(#34; Ajax&#34;也是如此),但通常不鼓励使用它们,特别是浏览器上下文。

你问为什么不好?

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

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

在下文中,我们将看看三种不同的解决方案,这些解决方案都是彼此叠加的:

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

这三种方法在当前浏览器中都可用,而节点7 +。


ES2017 +:承诺async/await

2017年发布的ECMAScript版本为异步功能引入了语法级支持。在asyncawait的帮助下,您可以在&#34;同步样式&#34;中编写异步。代码仍然是异步的,但它更容易阅读/理解。

async/await建立在承诺之上:async函数始终返回一个承诺。 await&#34; unwraps&#34;承诺,或者导致承诺被解决的价值,或者如果承诺被拒绝则抛出错误。

重要提示:您只能在await功能中使用async。目前,顶级await尚未受支持,因此您可能需要制作异步IIFE(Immediately Invoked Function Expression)才能启动async上下文。

您可以在MDN上详细了解asyncawait

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

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

当前browsernode版本支持async/await。您还可以借助regenerator(或使用再生器的工具,例如Babel)将代码转换为ES5来支持旧环境。


让函数接受回调

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

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

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

变为

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

这里我们定义了函数&#34; inline&#34;但你可以传递任何函数参考:

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

foo(myCallback);

foo本身定义如下:

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

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

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

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)的新功能,但它已经很好browser support了。还有许多库实现了标准的Promises API,并提供了其他方法来简化异步函数的使用和组合(例如bluebird)。

Promise是 future 值的容器。当promise接收到值(已解决)或取消(被拒绝)时,它会通知所有&#34;听众&#34;谁想要访问这个值。

优于普通回调的优势在于它们允许您解耦代码并且更容易编写。

以下是使用承诺的简单示例:

function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}

delay()
  .then(function(v) { // `delay` returns a promise
    console.log(v); // Log the value once it is resolved
  })
  .catch(function(v) {
    // Or do something else if it is rejected 
    // (it would not happen in this example, since `reject` is not called).
  });

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

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

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

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

有关承诺的更多信息:HTML5 rocks - JavaScript Promises

附注:jQuery的延迟对象

Deferred objects是jQuery的promises自定义实现(在Promise API标准化之前)。它们的行为几乎与承诺相似,但暴露出稍微不同的API。

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

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()在检查&#39; /密码时不会冻结代码。服务器上的页面 - 它向服务器发送请求,当它等待时,立即返回一个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
});

不推荐:同步&#34; Ajax&#34;呼叫

正如我所提到的,一些(!)异步操作具有同步对应物。我并不主张使用它们,但为了完整性,为什么你要执行同步调用:

没有jQuery

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

的jQuery

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

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

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

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

答案 1 :(得分:1009)

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

Note, for those using the new fetch API, Angular or promises I've added another answer below


你面临的是什么

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

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

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

这是一个简单的类比

success

(Fiddle)

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

此问题的一个可能解决方案是编码重新,告诉您的程序在计算完成时该怎么做。

a=5

这称为CPS。基本上,我们正在传递function onComplete(a){ // When the code completes, do this alert(a); } function getFive(whenDone){ var a; setTimeout(function(){ a=5; whenDone(a); },10); } 一个动作,当它完成时,我们告诉我们的代码在事件完成时如何反应(比如我们的AJAX调用,或者在这种情况下是超时)。

用法是:

getFive

哪个应警告“5”到屏幕。 (Fiddle)

可能的解决方案

基本上有两种解决方法:

  1. 使AJAX调用同步(让我们称之为SJAX)。
  2. 使用回调重构代码以使其正常工作。
  3. 1。同步AJAX - 不要这样做!!

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

      

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

         

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

    如果来执行此操作,您可以传递一个标记:Here is how:

    getFive(onComplete);
    

    2。重组代码

    让你的函数接受回调。在示例代码中,可以使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); } 接受回调。当foo完成时,我们将告诉我们的代码如何做出反应

    所以:

    foo

    变为:

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

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

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

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

    现在,让我们定义foo自己采取相应的行动

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

    (fiddle)

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

    如果您仍然很难在MDN了解此read the AJAX getting started guide

答案 2 :(得分:367)

XMLHttpRequest 2 (首先阅读Benjamin Gruenbaum&amp; Felix 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&amp;&amp; status)
  4. 还有一些其他情况我不记得让XMLHttpRequest 1烦人。
  5. 有两种方法可以获得此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 advanced features

    它支持所有*现代浏览器。我可以确认,因为我存在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)
    }
    

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

    用法示例:

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

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

    说完了......为什么不这么简单呢?


    如评论中所述,使用错误&amp;&amp;同步确实完全打破了答案的要点。哪个是以正确方式使用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);
    

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

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

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

    即使你通过&#39; POSTAPAPAP&#39;作为方法,它不会抛出错误。

    即使你通过了fdggdgilfdghfldj&#39;作为formdata,它不会抛出错误。

    在第一种情况下,错误位于displayAjax()下的this.statusText Method not Allowed内。

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

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

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

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

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

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

    PS:作为我写的第一个测试x(&#39; x&#39;,displayAjax)......,它完全得到了回复...... ???所以我检查了HTML所在的文件夹,并且有一个名为&#39; x.xml&#39;的文件。因此,即使您忘记了文件的扩展名,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响应......(我添加了一个简单的方法。)

答案 3 :(得分:295)

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

这意味着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处理程序尚未执行。这反过来意味着您返回的值未及时设置为正确的值。

以下是该问题的简单类比:

&#13;
&#13;
    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
&#13;
&#13;
&#13;

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

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

此问题的一个可能解决方案是编码重新,告诉您的程序在计算完成时该怎么做。承诺通过本质上是暂时的(时间敏感的)来积极地实现这一点。

快速回顾承诺

承诺是值随着时间的推移。承诺具有状态,它们在没有价值的情况下开始等待并且可以解决:

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

承诺只能更改状态一次,之后它将永远保持在同一状态。您可以将then处理程序附加到promises以提取其值并处理错误。 then个处理程序允许chaining个来电。承诺由using APIs that return them创建。例如,更现代的AJAX替换fetch或jQuery&#39; $.get返回承诺。

当我们在承诺上调用.then并从返回时,我们会收到已处理值的承诺。如果我们回到另一个承诺,我们会得到惊人的东西,但让我们抓住我们的马匹。

使用承诺

让我们看看如何通过承诺解决上述问题。首先,让我们通过使用Promise constructor创建延迟函数来证明我们对上述承诺状态的理解:

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转换为使用promises之后,我们可以使用then来计算它:

&#13;
&#13;
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;
});
&#13;
&#13;
&#13;

基本上,我们不是返回由于并发模型而无法执行的,而是返回包装器以获取值我们可以使用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`
})

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

ES2015(ES6)

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

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,....上返回迭代器的函数。虽然这本身很有趣并且为很多可能性开辟了空间,但有一个特别有趣的案例。

如果我们生成的序列是一系列动作而不是数字 - 我们可以在每次动作时暂停该动作并在我们恢复该动作之前等待它。因此,我们需要一系列 future 值,而不是一系列数字 - 即:promises。

这个有点棘手但非常强大的技巧让我们以同步的方式编写异步代码。有几个&#34;跑步者&#34;为你做到这一点,写一个代码只需几行代码,但超出了这个答案的范围。我将在这里使用Bluebird的Promise.coroutine,但还有其他包装,如coQ.async

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

此方法返回一个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承诺。这只是&#34;糖&#34; (通过添加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
}

它仍然会返回一个承诺:)

答案 4 :(得分:233)

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

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

答案 5 :(得分:222)

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

答案 6 :(得分:201)

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

enter image description here

答案 7 :(得分:146)

Angular1

对于使用AngularJS的用户,可以使用Promises来处理这种情况。

Here它说,

  

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

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

下面提到的docs中的示例。

  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中查看以下示例,但recommended使用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));
}

请在此处查看original帖子。但是Typescript不支持native es6 Promises,如果你想使用它,你可能需要插件。

此外,此处还有承诺spec

答案 8 :(得分:126)

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

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

示例:

&#13;
&#13;
// 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));
}
&#13;
.as-console-wrapper {
  max-height: 100% !important;
}
&#13;
&#13;
&#13;

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

示例:

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

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

(我们可以取消使用expecting并使用results.length === theArray.length,但这使得我们可以在调用未完成时更改theArray的可能性... )

注意我们如何使用index中的forEachresults中的结果保存在与其相关的条目相同的位置,即使结果无序到达(从异步调用并不一定按照启动顺序完成。

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

示例:

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

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

或者这是一个返回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拒绝承诺。)

示例:

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

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

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

如果doSomethingAsync为您提供Promise,则可以使用Promise.all

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

如果您知道doSomethingAsync将忽略第二个和第三个参数,您可以直接将其传递给mapmap使用三个参数调用其回调,但大多数人只使用第一大部分时间):

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

示例:

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

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

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

示例:

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

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

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

如果doSomethingAsync为您提供了承诺,如果您可以使用ES2017 +语法(可能使用Babel等转录器),则可以async function使用for-of和{ {3}}:

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

示例:

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

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

如果你还不能使用ES2017 +语法,你可以使用await的变体(这比通常的Promise减少更复杂,因为我们没有传递结果一个进入下一个,但在数组中收集他们的结果):

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

示例:

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

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

...使用"Promise reduce" pattern

时不那么麻烦
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);
});

示例:

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

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

答案 9 :(得分:101)

看一下这个例子:

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/

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

答案 10 :(得分:88)

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

这是一个相同的例子:

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

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

答案 11 :(得分:83)

这是数据绑定的两种方式在许多新的JavaScript框架中使用的地方之一对你来说很有用......

因此,如果您使用 Angular,React 或任何其他框架两种方式数据绑定存储概念,则只需解决此问题对你来说,所以简单来说,你的结果是undefined在第一阶段,所以你在收到数据之前得到result = undefined,然后一旦得到结果,它就会被更新,获得分配给您的Ajax调用响应的新值...

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

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

例如,在您使用 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 的更多信息,这些是执行此异步内容的新方法。

答案 12 :(得分:80)

虽然承诺和回调在许多情况下都能很好地发挥作用,但在表达类似内容时会很痛苦:

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
}

您可以结帐项目here

答案 13 :(得分:76)

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

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

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

答案 14 :(得分:73)

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

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

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

上下文。示例是查询Spotify Web API端点,以便为给定的查询字符串集搜索playlist个对象:

[
 "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}" 

因此,您要运行以下示例,您需要将访问令牌放在请求标头中:

&#13;
&#13;
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);
  });
&#13;
<div id="console" />
&#13;
&#13;
&#13;

我已经广泛讨论了这个解决方案here

答案 15 :(得分:70)

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

中完全按照您的要求执行操作

这很简单:

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

这是您的代码的工作版本:

(async function(){

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

})()

await is supported in all current browsers and node 8

答案 16 :(得分:63)

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

答案 17 :(得分:58)

  

Js是一个单线程。

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

1)事件循环

2)Web API

3)事件队列

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

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

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

因此解决方法是回调承诺

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

CallBack

 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在顶部回答) https://stackoverflow.com/a/14220323/7579856

答案 18 :(得分:55)

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

如果基础功能被宣传

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

&#13;
&#13;
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');
});
&#13;
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
&#13;
&#13;
&#13;

如果基础功能没有被宣传

步骤1.将回调函数包装回nsynjs-aware包装器中(如果它具有promisified版本,则可以跳过此步骤):

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

答案 19 :(得分:42)

这是我们在与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 。 Promise实际上意味着它意味着什么:它意味着该功能可以保证您提供将来获得的任何输出。所以让我们看看上面的小问题:

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的调用等,你可以使用resolve值的承诺(你打算返回)。因此,简而言之,您在异步函数中解析值而不是返回

UPDATE(与异步/等待的承诺)

除了使用then/catch处理promises之外,还有一种方法。在移动到下一行代码之前,我们的想法是识别异步函数,然后等待promises 解析。它仍然只是引擎盖下的promises,但采用了不同的语法方法。为了使事情更清楚,您可以在下面找到比较:

然后/抓住版本:

function fetchUsers(){
   let users = [];
   getUsers()
   .then(_users => users = _users)
   .catch(err =>{
      throw err
   })
   return users;
 }

async / await version:

  async function fetchUsers(){
     try{
        let users = await getUsers()
        return users;
     }
     catch(err){
        throw err;
     }
  }

答案 20 :(得分:36)

以下是使用异步请求的一些方法:

  1. Browser Promise object
  2. Q - JavaScript的承诺库
  3. A+ Promises.js
  4. jQuery deferred
  5. XMLHttpRequest API
  6. 使用回调概念 - 作为第一个答案中的实现
  7. 示例: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();

答案 21 :(得分:36)

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-compiler运行代码来生成ECMAScript 5.

回调...args包装在一个数组中,并在读取它们时进行解构,以便模式可以处理具有多个参数的回调。例如node fs

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

答案 22 :(得分:33)

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

答案 23 :(得分:33)

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

答案 24 :(得分:27)

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

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

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

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

因为JS无法知道在order_milk执行put_in_coffee之前需要等待才能完成。换句话说,它不知道order_milk异步 - 在未来某个时间之前不会产生牛奶。 JS和其他声明性语言在不等待的情况下执行一个接一个的语句。

这个问题的经典JS方法,利用JS支持函数作为可以传递的第一类对象的事实,是将函数作为参数传递给异步请求,然后它将在调用时调用它已经在将来的某个时候完成了它的任务。这就是&#34;回调&#34;做法。它看起来像这样:

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

输入承诺

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

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

order_milk() . then(put_in_coffee)

这样做的一个优点是我们可以将这些组合在一起以创建未来发生的序列(&#34;链接&#34;):

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

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

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

实际上,我们所做的一切都是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);

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

答案 25 :(得分:26)

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

function foo() {
    var result;

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

    return result;
}

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

答案 26 :(得分:26)

在阅读了这里的所有回复以及我的经验之后,我想继续介绍 callback, promise and async/await用于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()(根据需要的.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的糖语法!

异步函数为我们提供了简洁明了的语法,使我们可以编写更少的代码来实现与诺言相同的结果。 异步/等待看起来类似于同步代码,并且同步代码更容易读写。要捕获异步/等待错误,我们可以使用块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请求)

答案 27 :(得分:24)

问题是:

  

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

可以解释为:

  

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

解决方案是避免回调,并使用承诺 async / 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")

答案 28 :(得分:14)

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

The Event Loop and Concurrency Model

您需要注意三件事; 队列; the event loop和堆栈

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

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会依赖于响应,然后才能继续。

最大的问题是什么时候能得到答复?答案是我们不知道 - 所以事件循环正在等待那条消息说'#34;嘿跑我&#34;。如果JS只是同步等待那条消息你的应用程序会冻结,它会很糟糕。因此,JS继续执行队列中的下一个项目,同时等待消息被添加回队列。

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

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

因为回调不会立即执行,但是稍后将引用传递给函数并不重要。所以

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

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

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

答案 29 :(得分:14)

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

$this

并按此执行。

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

或Promise语法

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

答案 30 :(得分:14)

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

这里有很多信息丰富的答案,我不会重复其中任何一个。在JavaScript中编程的关键是首先使用正确的心理模型进行整体执行。

  1. 您的入口点是作为事件的结果执行的。对于 例如,带有代码的脚本标记被加载到浏览器中。 (因此,这就是你可能需要关心的原因 如果页面需要dom元素,则可以运行代码 首先建造,等等。)
  2. 您的代码执行完毕 - 但是许多异步调用它 make - 不执行任何的回调,包括XHR 请求,设置超时,dom事件处理程序等。等待执行的每个回调都将位于一个队列中,等待轮流在其他触发的事件全部执行完毕后运行。
  3. 每个单独回调XHR请求,设置超时或dom 一旦被调用,该事件将运行完成。
  4. 好消息是,如果你理解这一点,你将永远不必担心竞争条件。首先,您应该如何组织代码本质上是对不同离散事件的响应,以及您希望如何将它们组合成逻辑序列。您可以使用promises或更高级别的新异步/等待作为工具,或者您可以自己动手。

    但是,在您对实际问题域感到满意之前,您不应该使用任何战术工具来解决问题。绘制这些依赖关系的映射,以了解何时需要运行。尝试对所有这些回调采取临时方法并不能很好地为您服务。

答案 31 :(得分:13)

使用承诺

此问题的最完美答案是使用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;
    }();
}

答案 32 :(得分:9)

没有方法可以从函数返回ajax响应的结果,之所以不可能,是因为Ajax调用($ .get()或$ .post())是异步调用甚至在呈现响应之前,调用封装Ajax调用的函数都会返回。

在这种情况下,唯一的选择是返回一个promise对象,该对象将在响应到达时解析。

有两种方法可以解决上述问题,在两种情况下都可以利用promise。

所提供的两个代码段都可以使用,包括josn url,并且可以直接复制到jsfiddle并进行测试。

选项#1:直接从foo方法返回Ajax调用,在最新版本的Jquery中,ajax调用返回一个promise对象,可以使用.then函数解析该对象。 .then函数的输入参数是对resolve函数的调用。

    //decalre function foo
   function foo(url)
   {
     return $.get(url);
   }

   //invoke foo function, which returns a promise object
   //the then function accepts the call back to the resolve function
   foo('https://jsonplaceholder.typicode.com/todos/1')
   .then(function(response)
   {
    console.log(response);
   })

选项#2-在函数内部声明一个Promise对象,并将Ajax调用封装在该Promise函数中,并返回Promise对象。

   function foo1() {

   var promise = new Promise(function(resolve, reject)
   {

     $.ajax({
    url: 'https://jsonplaceholder.typicode.com/todos/1',
    success: function(response) {
        console.log(response);

        resolve(response);
        // return response; // <- I tried that one as well
      }
    });

   });



   return promise;
   }

   foo1()
   .then(function(response)
   {
     console.log('Promise Resolved');
     console.log(response);
   })

答案 33 :(得分:7)

这是一个有效的示例:

const validateName = async userName => {
  const url = "abc/xyz";
  try {
    const response = await axios.get(url);
    return response.data
  } catch (err) {
    return false;
  }
};

validateName("user")
 .then(data => console.log(data))
 .catch(reason => console.log(reason.message))

答案 34 :(得分:6)

最初,回调用于异步操作(例如,在XMLHttpRequest API中)。现在,基于承诺的API(如浏览器的Fetch API)已成为默认解决方案,并且所有现代浏览器以及Node.J(服务器端)都支持更好的async/await语法。

一种常见的情况-从服务器获取JSON数据-如下所示:

async function fetchResource(url) {
  const res = await fetch(url);
  if (!res.ok) {
    throw new Error(res.statusText);
  }
  return res.json();
}

要在其他功能中使用它:

async function doSomething() {
  try {
    const data = await fetchResource("https://example.test/resource/1");
    // ...
  } catch (e) {
    // handle error
    ...
  }
}

如果您设计现代的API,则强烈建议您优先使用基于承诺的样式,而不要使用回调。如果您继承了依赖于回调的API,则可以将其包装为Promise:

function sleep(timeout) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, timeout);
  });
}

async function fetchAfterTwoSeconds(url) {
  await sleep(2000);
  return fetchResource(url);
}

在历来仅依赖于回调的Node.Js中,该技术非常普遍,以至于他们添加了一个名为util.promisify的辅助函数。

答案 35 :(得分:4)

请求以异步方式工作,因此您不能像典型代码一样以同步方式读取数据。但是,使用async/await可以创建看起来与同步代码相似的异步代码。需要处理请求数据的代码需要由异步功能(如以下代码段中的load进行包装),并且在其中需要在await之前添加foo() keywort(也使用async/await )。

async function foo() {
  var url= 'https://jsonplaceholder.typicode.com/todos/1';
  var result= await (await fetch(url)).text(); // or .json()
  return result;
}

async function load() {
  var result = await foo();
  console.log(result);
}

load();

答案 36 :(得分:4)

我认为,无论使用哪种方法或机制,或是将其隐藏起来的任何框架(Angular / React),都应遵循以下原则:

  1. 在程序流程中(想想代码甚至是最低级别:机器代码),数据可能不会在2秒后,3秒后返回,或者根本无法到达,因此没有通常使用return来返回数据。

  2. 这是经典的“观察者模式”。 (它可以采用“回调”的形式。)它是:“嘿,我有兴趣知道数据是否成功到达;何时通知我。因此,您注册了一个将被通知的观察者(或调用了用于通知数据成功到达的函数。)通常,您还注册了一个此类数据到达失败的观察者。

  3. 当成功到达数据或失败返回此类数据时,已注册的观察者(或回调)将与数据一起被通知(或与数据一起被调用)。如果观察者以回调函数foo的形式注册,则将调用foo(data)。如果观察者以对象foo的形式注册,则取决于接口,可能是调用了foo.notify(data)

答案 37 :(得分:2)

异步:错误

我通过设置sync false来解决我的ajax调用,并对其进行了重组:

我设置了名为sendRequest(type, url, data)的全局函数,其中每个地方每次都调用三个参数

function sendRequest(type, url, data) {
    let returnValue = null;
    $.ajax({
        url: url,
        type: type,
        async: false,
        data: data,
        dataType: 'json',
        success: function (resp) {
            returnValue = resp;
        }
    });
    return returnValue;
} 

立即调用功能

let password = $("#password").val();
        let email = $("#email").val();
        let data = {
            email: email,
            password: password,
        };
        let  resp =  sendRequest('POST', 'http://localhost/signin')}}", data);
        console.log(resp);

代码中重要的注释是:async: false

答案 38 :(得分:1)

看看这个例子:

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 way (async - await)

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

答案 39 :(得分:-1)

async/await与像Babel这样的编译器一起使用,以使其在较旧的浏览器中运行。您还必须从npm安装此Babel预设和polyfill:npm i -D babel-preset-env babel-polyfill。

function getData(ajaxurl) { 
  return $.ajax({
    url: ajaxurl,
    type: 'GET',
  });
};

async test() {
  try {
    const res = await getData('https://api.icndb.com/jokes/random')
    console.log(res)
  } catch(err) {
    console.log(err);
  }
}

test();

.then回调只是编写相同逻辑的另一种方式。

getData(ajaxurl).then(function(res) {
    console.log(res)
}

答案 40 :(得分:-2)

将Node上的XHR转换为async-await的简单代码示例

var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
var xhttp = new XMLHttpRequest();

function xhrWrapWithPromise() {
  return new Promise((resolve, reject) => {
    xhttp.onreadystatechange = function() {
      if (this.readyState == 4) {
        if (this.status == 200) {
          resolve(this.responseText);
        } else {
          reject(new Error("Couldn't feth data finally"));
        }
      }
    };
    xhttp.open("GET", "https://www.w3schools.com/xml/xmlhttp_info.txt", true);
    xhttp.send();
  });
}

//We need to wrap await in Async function so and anonymous IIFE here
(async _ => {
  try {
    let result = await xhrWrapWithPromise();
    console.log(result);
  } catch (error) {
    console.log(error);
  }
})();

答案 41 :(得分:-4)

由于'await'总是返回Promise,因此只需执行一个额外的'await'(在异步函数内部)以提取值:

test(); // This alerts "hello"

// This is the outer function that wants to get the string result of inner()
async function test() {
    var str=await await inner();
    alert(str);
    } // test

// This ia an inner function that can do arbitrary async operations
async function inner() {
    return Promise.resolve('hello');
    }