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

loading...


4577

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

loading...

30回答

  • 17

    Js是一个单线程 .

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

    1)事件循环

    2)Web API

    3)事件队列

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

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

    假设我们的serverRequest()函数在代码中有一个return语句,当我们从服务器获取数据时,Web API将在队列末尾将其推送到队列中 . 当它在队列末尾被推送时我们无法利用它的数据,因为我们的队列中没有剩余的功能来利用这些数据 . Thus it is not possible to return something from Async Call.

    因此,对此的解决方案是回调或承诺 .

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

    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


  • 262

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

    如果潜在的功能被宣传

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

    function synchronousCode() {
    
        var getURL = function(url) {
            return window.fetch(url).data.text().data;
        };
        
        var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
        console.log('received bytes:',getURL(url).length);
        
    };
    
    nsynjs.run(synchronousCode,{},function(){
        console.log('synchronousCode done');
    });
    
    <script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
    

    如果未实现基础功能

    步骤1.使用回调函数将函数包装到nsynjs-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.通过nnsynjs以同步方式运行函数:

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

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

    更多例子:https://github.com/amaksr/nsynjs/tree/master/examples


  • 29

    问题是:

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

    可以解释为:

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

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

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

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

    让我们首先启用JQuery用法,让 $ 可用作 S

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

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

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

    使用 asynchronous 代码,就好像它是 synchronous

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

  • 4904

    如果您正在使用承诺,这个答案适合您 .

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

    你的代码应该是这样的:

    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 处理程序总是异步执行 - 也就是说 after 它们下面的代码不在 .then 处理程序中 .

    这意味着当您返回 data then 处理程序时,您返回的've defined did not execute yet. This in turn means that the value you'尚未及时设置为正确的值 .

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

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

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

    自从在请求有机会告诉您的代码该值是什么之前,您还没有发生操作(AJAX,服务器调用,IO,计时器) .

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

    快速回顾承诺

    承诺是一种随时间变化的 Value . 承诺有状态,它们开始等待没有 Value ,可以满足:

    • fulfilled 表示计算成功完成 .

    • rejected 表示计算失败 .

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

    当我们在一个承诺上调用 .then 并从中返回一些东西时 - 我们得到了一个对已处理值的承诺 . 如果我们回到另一个承诺,我们就会 grab 我们的马匹 .

    承诺

    让's see how we can solve the above issue with promises. First, let'通过使用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 来计算它:

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

    基本上,而不是返回一个值,我们可以't do because of the concurrency model - we'返回一个包装器,我们可以用 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,.... 上的迭代器 . 虽然这本身很有趣并且为很多可能性开辟了空间,但有一个特别有趣的案例 .

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

    这个有点棘手但非常强大的技巧让我们以同步的方式编写异步代码 . 有几个"runners"为你做这个,写一个是几行代码,但超出了这个答案的范围 . 我'll be using Bluebird' s 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中,这是进一步标准化的,现在有几个提议,但在所有提案中你都可以承诺 . 通过添加 asyncawait 关键字,这只是上面ES6提案的"sugar"(更好的语法) . 制作上面的例子:

    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
    }
    

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


  • 54

    简短的回答是, you have to implement a callback like this:

    function callback(response) {
        // Here you can do what ever you want with the response object.
        console.log(response);
    }
    
    $.ajax({
        url: "...",
        success: callback
    });
    

  • 44

    如果您在代码中没有使用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 代表 asynchronous . 这意味着发送请求(或者更确切地说是接收响应)将从正常执行流程中取出 . 在您的示例中,.send立即返回,并且在您传递的函数之前执行下一个语句 return result; ,因为 success 回调甚至被调用 .

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

    这是一个简单的比喻

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

    (Fiddle)

    返回的 a 的值是 undefined ,因为 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);
    }
    

    这叫做CPS . 基本上,我们正在传递 getFive 一个动作,当它完成时,我们告诉我们的代码在事件完成时如何反应(比如我们的AJAX调用,或者在这种情况下是超时) .

    用法是:

    getFive(onComplete);
    

    哪个应警告"5"到屏幕 . (Fiddle) .

    可能的解决方案

    基本上有两种解决方法:

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

    • 重构代码以使用回调正常工作 .

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

    至于同步AJAX, don't do it! Felix 's answer raises some compelling arguments about why it'是一个坏主意 . 总而言之,它是'll freeze the user'浏览器,直到服务器返回响应并创建非常糟糕的用户体验 . 以下是MDN的另一个简短摘要:

    XMLHttpRequest支持同步和异步通信 . 但是,一般而言,出于性能原因,异步请求应优先于同步请求 . 简而言之,同步请求会阻止代码的执行......这可能会导致严重的问题......

    如果你必须这样做,你可以传递一个标志:Here is how:

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

    2.重组代码

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

    所以:

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

    变为:

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

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

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

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

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

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

    (fiddle)

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

    如果你仍然很难在MDN理解这个read the AJAX getting started guide .


  • 25

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

  • 165

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

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

    像这样执行它 .

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

    或Promise语法

    foo().then(response => {
        console.log(response)
    
    }).catch(error => {
        console.log(error)
    
    })
    

  • 322

    我写的下面的例子说明了如何

    • 处理异步HTTP调用;

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

    • 使用Promise模式;

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

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

    语境 . 示例是查询Spotify Web API endpoints ,以便为给定的查询字符串集搜索 playlist 对象:

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

    对于每个项目,新的Promise将触发一个块 - ExecutionBlock ,解析结果,根据结果数组计划一组新的promises,即Spotify user 对象列表,并在 ExecutionProfileBlock 内异步执行新的HTTP调用 .

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

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

    -H "Authorization: Bearer {your access token}"
    

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

    var spotifyAccessToken = "YourSpotifyAccessToken";
    var console = {
        log: function(s) {
            document.getElementById("console").innerHTML += s + "
    " } } // Simple XMLHttpRequest // based on https://davidwalsh.name/xmlhttprequest SimpleRequest = { call: function(what, response) { var request; if (window.XMLHttpRequest) { // Mozilla, Safari, ... request = new XMLHttpRequest(); } else if (window.ActiveXObject) { // Internet Explorer try { request = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { try { request = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {} } } // State changes request.onreadystatechange = function() { if (request.readyState === 4) { // Done if (request.status === 200) { // Complete response(request.responseText) } else response(); } } request.open('GET', what, true); request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken); request.send(null); } } //PromiseAll var promiseAll = function(items, block, done, fail) { var self = this; var promises = [], index = 0; items.forEach(function(item) { promises.push(function(item, i) { return new Promise(function(resolve, reject) { if (block) { block.apply(this, [item, index, resolve, reject]); } }); }(item, ++index)) }); Promise.all(promises).then(function AcceptHandler(results) { if (done) done(results); }, function ErrorHandler(error) { if (fail) fail(error); }); }; //promiseAll // LP: deferred execution block var ExecutionBlock = function(item, index, resolve, reject) { var url = "https://api.spotify.com/v1/" url += item; console.log( url ) SimpleRequest.call(url, function(result) { if (result) { var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) { return item.owner.href; }) resolve(profileUrls); } else { reject(new Error("call error")); } }) } arr = [ "search?type=playlist&q=%22doom%20metal%22", "search?type=playlist&q=Adele" ] promiseAll(arr, function(item, index, resolve, reject) { console.log("Making request [" + index + "]") ExecutionBlock(item, index, resolve, reject); }, function(results) { // Aggregated results console.log("All profiles received " + results.length); //console.log(JSON.stringify(results[0], null, 2)); ///// promiseall again var ExecutionProfileBlock = function(item, index, resolve, reject) { SimpleRequest.call(item, function(result) { if (result) { var obj = JSON.parse(result); resolve({ name: obj.display_name, followers: obj.followers.total, url: obj.href }); } //result }) } //ExecutionProfileBlock promiseAll(results[0], function(item, index, resolve, reject) { //console.log("Making request [" + index + "] " + item) ExecutionProfileBlock(item, index, resolve, reject); }, function(results) { // aggregated results console.log("All response received " + results.length); console.log(JSON.stringify(results, null, 2)); } , function(error) { // Error console.log(error); }) ///// }, function(error) { // Error console.log(error); });
    <div id="console" />
    

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


  • 195

    这是许多新的JavaScript框架中使用的 two ways data binding 将为您提供很大帮助的地方之一...

    所以,如果您正在使用 Angular, React 或任何其他框架 two ways data binding, 这个问题只是为您修复,那么简单来说,您的结果在第一阶段是 undefined ,所以在收到数据之前您已经 result = undefined ,那么只要你得到结果,它会更新并分配给你的Ajax调用响应的新值...

    但是你怎么能在纯 javascriptjQuery 这样做,例如你在这个问题中提到的?

    您可以使用 callbackpromise 和最近 observable 为您处理它,例如在promises中我们有一些函数,如success()或then(),它们将在您的数据准备就绪时执行,与回调或 subscribe 函数相同在 observable .

    例如,在您使用 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
    });
    

    有关 promisesobservables 的更多信息,这些是执行此异步内容的新方法 .


  • 30

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

    这是一个相同的例子:

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

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


  • 27

    在与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()的立即返回的内容,而不是后来发生的任何其他内容 .

    So, how do we tackle this issue?

    让我们问一下 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 的值(你想要返回的值) . 因此,简而言之,在异步函数中, resolve 值而不是 returning .

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

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

    then / catch版本:

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

    async / await版本:

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

  • 60

    Have a look at this example:

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

    如您所见 getJokereturning a 已解决 promise (返回 res.data.value 时会解析) . 所以你要等到 $http.get 请求完成然后执行 console.log(res.joke) (作为正常的异步流程) .

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

  • 63

    foo() 成功中使用 callback() 函数 . 试试这种方式 . 它简单易懂 .

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

  • 19

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

  • 10

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

    示例: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();
    

  • 8

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

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

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

    如你看到的:

    • 它比列出的所有其他功能短 .

    • 直接设置回调(因此没有额外的不必要的闭包) .

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

    • 还有其他一些我不记得的情况让XMLHttpRequest 1烦人 .

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

    最简单的:

    this.response
    

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

    e.target.response
    

    例:

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

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

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

    没什么比这更容易

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

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

    再次......这是一个非常短的功能,但它确实得到和发布 .

    用法示例:

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

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

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


    正如评论中所提到的,使用error && synchronous确实完全打破了答案的要点 . 这是一个很好的短片如何以正确的方式使用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);
    

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

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

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

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

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

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

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

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

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

    只有 this.type 设置为错误 .

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

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

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


    同步读取文件

    Don't do that.

    如果你想阻止浏览器一段时间加载一个漂亮的大 .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,数组缓冲区,你可以设置 Headers ,覆盖mimetype,还有更多......

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


  • 34

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

    事件循环和并发模型

    你需要注意三件事; The queue; the event loop and the stack

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

    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 )都会被压入堆栈 . 执行,然后忘记 - 事件循环将移动到队列中的下一个事物(或侦听消息)

    The key thing here is the order of execution. That is

    什么时候会发生

    当您使用AJAX向外部方进行调用或运行任何异步代码(例如setTimeout)时,Javascript依赖于响应,然后才能继续 .

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

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

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

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

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

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

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


  • 100

    Short answer :您的 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 中 .


  • 67

    Let's see the forest first before looking at the trees.

    这里有很多信息丰富的答案和细节,我不再重复 . 在JavaScript中编程的关键是首先执行 correct mental model 整体执行 .

    • 您的入口点是作为事件的结果执行的 . 例如,带有代码的脚本标记将加载到浏览器中 . (因此,这就是为什么您可能需要关注页面是否准备好运行代码,如果它需要首先构造dom元素,等等)

    • 您的代码执行完成 - 无论它进行多少次异步调用 - 都不执行回调的 any ,包括XHR请求,设置超时,dom事件处理程序等 . 等待执行的每个回调都将位于队列中,在其他被解雇的事件完成执行后等待轮到他们运行 .

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

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

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


  • 19

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

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

    例:

    // WRONG
    var theArray = [1, 2, 3];
    var results = [];
    theArray.forEach(function(entry) {
        doSomethingAsync(entry, function(result) {
            results.push(result);
        });
    });
    console.log("Results:", results); // E.g., using them, returning them, etc.
    
    function doSomethingAsync(value, callback) {
        console.log("Starting async operation for " + value);
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            callback(value * 2);
        }, Math.floor(Math.random() * 200));
    }
    
    .as-console-wrapper {
      max-height: 100% !important;
    }
    

    不起作用的原因是来自 doSomethingAsync 避免't run yet by the time you'的回调试图使用结果 .

    因此,如果您有一个数组(或某种类型的列表)并希望对每个条目执行异步操作,则有两个选项:并行(重叠)或串行(按顺序一个接一个)执行操作 .

    平行

    您可以启动所有这些并跟踪您期望的回调次数,然后在获得许多回调时使用结果:

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

    例:

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

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

    注意我们如何使用 indexforEach 将结果保存在 results 与其相关的条目相同的位置,即使结果无序到达(因为异步调用不一定按照它们的启动顺序完成) ) .

    但是如果你需要从函数中返回那些结果呢?正如其他答案所指出的,你不能;你必须让你的函数接受并调用回调(或返回Promise) . 这是一个回调版本:

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

    例:

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

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

    例:

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

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

    如果 doSomethingAsync 给你一个Promise,你可以使用Promise.all

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

    例:

    function doSomethingWith(theArray) {
        return Promise.all(theArray.map(function(entry) {
            return doSomethingAsync(entry, function(result) {
                results.push(result);
            });
        }));
    }
    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));
        });
    }
    
    .as-console-wrapper {
      max-height: 100% !important;
    }
    

    请注意 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; ,但是在下面的一些例子中我们没有要使用的索引 . )

    例:

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

    (或者,再次为 doSomethingAsync 构建一个包装器,它会给你一个承诺并做下面的...)

    如果 doSomethingAsync 给你一个Promise,如果你可以使用ES2017语法(可能有一个像Babel这样的转换器),你可以使用async functionfor-ofawait

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

    例:

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

    如果你还不能使用ES2017语法,你可以在"Promise reduce" pattern上使用一个变体(这比通常的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);
    });
    

    例:

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

    ... ES2015+ arrow functions不那么麻烦:

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

    例:

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

  • 204

    我会回答一个看起来很可怕的手绘漫画 . 第二个图像是 result 在您的代码示例中为 undefined 的原因 .


  • 72

    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使用 ObservablesAngular2 .

    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定义 .


  • 49

    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


  • 81

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

    function foo() {
        var result;
    
        $.ajax({
            url: '...',
            success: function(response) {
                myCallback(response);
            }
        });
    
        return result;
    }
    
    function myCallback(response) {
        // Does something.
    }
    

  • 46

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

  • 57

    →有关不同示例的异步行为的更一般说明,请参阅我在函数内部修改变量之后为什么变量不变? - 异步代码参考→如果您已经了解该问题,请跳至下面的可能解决方案 .

    问题

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

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

    同步

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

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

    function findItem() {
        var item;
        while(item_not_found) {
            // search
        }
        return item;
    }
    
    var item = findItem();
    
    // Do something with item
    doSomethingElse();
    

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

    异步

    你出于同样的原因再次打电话给你的朋友 . 但是这次你告诉他你很匆忙,他应该用手机给你回电话 . 你挂断了,离开了房子,做了你打算做的事情 . 一旦你的朋友给你回电话,你正在处理他给你的信息 .

    这正是您执行Ajax请求时发生的情况 .

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

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


    解决方案

    Embrace the asynchronous nature of JavaScript! 虽然某些异步操作提供了同步对应("Ajax"也是如此),但通常不鼓励使用它们,尤其是在浏览器上下文中 .

    你问为什么这么糟糕?

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

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

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

    • Promises with async/await (ES2017,如果使用转换器或再生器,可在旧版浏览器中使用)

    • Callbacks (在节点中很受欢迎)

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

    All three are available in current browsers, and node 7+.


    ES2017:使用async / await进行承诺

    2017年发布的ECMAScript版本引入了异步函数的语法级支持 . 在 asyncawait 的帮助下,您可以在"synchronous style"中编写异步 . 代码仍然是异步的,但它更容易阅读/理解 .

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

    Important: 您只能在 async 函数中使用 await . 目前,尚不支持顶级 await ,因此您可能必须创建一个异步IIFE才能启动 async 上下文 .

    您可以在MDN上阅读有关asyncawait的更多信息 .

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

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

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

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

    foo 本身定义如下:

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

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

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

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

    使用回调编写代码比使用它看起来更容易 . 毕竟,浏览器中的JavaScript是由事件驱动的(DOM事件) . 接收Ajax响应只不过是一个事件 .
    当您必须使用第三方代码时可能会出现困难,但大多数问题可以通过思考应用程序流来解决 .


    ES2015:承诺与then()

    Promise API是ECMAScript 6(ES2015)的新功能,但它已经有了很好的browser support . 还有许多库实现了标准的Promises API,并提供了其他方法来简化异步函数的使用和组合(例如bluebird) .

    承诺是未来 Value 观的容器 . 当promise接收到值(已解决)或取消(拒绝)时,它会通知所有想要访问此值的"listeners" .

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

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

    function delay() {
      // `delay` returns a promise
      return new Promise(function(resolve, reject) {
        // Only `delay` is able to resolve or reject the promise
        setTimeout(function() {
          resolve(42); // After 3 seconds, resolve the promise with value 42
        }, 3000);
      });
    }
    
    delay()
      .then(function(v) { // `delay` returns a promise
        console.log(v); // Log the value once it is resolved
      })
      .catch(function(v) {
        // Or do something else if it is rejected 
        // (it would not happen in this example, since `reject` is not called).
      });
    

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

    function ajax(url) {
      return new Promise(function(resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.onload = function() {
          resolve(this.responseText);
        };
        xhr.onerror = reject;
        xhr.open('GET', url);
        xhr.send();
      });
    }
    
    ajax("/echo/json")
      .then(function(result) {
        // Code depending on result
      })
      .catch(function() {
        // An error occurred
      });
    

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

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

    旁注:jQuery的延迟对象

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

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

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

    旁注:承诺陷阱

    请记住,promises和deferred对象只是未来值的容器,它们本身并不是值 . 例如,假设您有以下内容:

    function checkPassword() {
        return $.ajax({
            url: '/password',
            data: {
                username: $('#username').val(),
                password: $('#password').val()
            },
            type: 'POST',
            dataType: 'json'
        });
    }
    
    if (checkPassword()) {
        // Tell the user they're logged in
    }
    

    此代码误解了上述异步问题 . 具体来说,服务器上的 $.ajax() 不't freeze the code while it checks the ' / password'页面 - 它向服务器发送请求,当它等待时,立即返回一个jQuery Ajax Deferred对象,而不是服务器的响应 . 这意味着 if 语句将始终获取此Deferred对象,将其视为 true ,并继续进行,就像用户已登录一样 . 不好 .

    但修复很简单:

    checkPassword()
    .done(function(r) {
        if (r) {
            // Tell the user they're logged in
        } else {
            // Tell the user their password was bad
        }
    })
    .fail(function(x) {
        // Tell the user something bad happened
    });
    

    不推荐:同步“Ajax”调用

    正如我所提到的,一些(!)异步操作有同步对应物 . 我并不主张使用它们,但为了完整起见,以下是执行同步调用的方法:

    没有jQuery

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

    jQuery

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

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

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

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


  • 928

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

    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 .


  • 122

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

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

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

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

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

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

    order_milk(put_in_coffee);
    

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

    这种回调方法的问题在于它污染了用 return 报告其结果的函数的正常语义;相反,函数不能通过调用作为参数给出的回调来报告其结果 . 而且,当处理较长的事件序列时,这种方法会迅速变得难以处理 . 例如,假设我想等待将牛奶放入咖啡中,然后再进行第三步,即喝咖啡 . 我最终需要写这样的东西:

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

    我将牛奶放入 put_in_coffee 的地方,以及牛奶放入后执行的动作( drink_coffee ) . 这样的代码变得难以编写,读取和调试 .

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

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

    输入承诺

    这是"promise"概念的动机,"promise"是一种特殊类型的 Value ,它代表了某种类型的 futureasynchronous 结果 . 它可以代表已经发生的事情,或者将来会发生的事情,或者根本不会发生的事情 . Promise有一个名为 then 的方法,当promise表示的结果已经实现时,你传递一个动作 .

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

    order_milk() . then(put_in_coffee)
    

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

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

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

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

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

    get_data() . then(do_something)
    

    例如,

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

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

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

    async关键字

    但是,如果是同步的,必须以一种方式编写代码,如果是异步的,那么仍然会有一种模糊的不满 . 对于同步,我们写

    a();
    b();
    

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

    a() . then(b);
    

    上面,我们说,“JS无法知道在执行第二次调用之前第一次调用需要 wait ” . 如果 was 某种方式告诉JS那会不会很好?事实证明,有一个 await 关键字,在一个叫做"async"函数的特殊函数中使用 . 此功能是即将推出的ES版本的一部分,但已经在诸如Babel之类的转发器中提供了正确的预设 . 这让我们可以简单地写

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

    在你的情况下,你可以写出类似的东西

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

  • 9

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

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

评论

暂时没有评论!