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

共30个回答,已解决, 标签: 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`.
第1个答案(采用)

→ 有关异步行为的更一般的解释与不同的例子, 请参见为什么在函数内部修改变量后, 我的变量不会改变?-异步代码引用

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

问题在于

Ajax中的a表示异步。这意味着发送请求 (或者更确切地说, 接收响应) 将从正常的执行流中取出。在您的示例中, $.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 调用执行后执行语句。为了最终获得响应, 您提供了一个函数, 以便在收到响应后调用, 一个回调(注意什么?回拨? )。在调用回调之前, 将执行该调用之后的任何语句。


解决方案

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

你为什么要问?

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

所有这些都是非常糟糕的用户体验。用户将无法判断是否一切正常。此外, 对于连接速度慢的用户来说, 这种影响会更严重。

在下面, 我们将介绍三种不同的解决方案, 它们都是在彼此之上构建的:

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

这三种方法在当前浏览器中都可用,

第2个答案

如果您没有在代码中使用 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'

费利克斯·克林为使用 AJAX JQUERY 的人写了一个很好的答案, 我决定为那些没有 JQUERY 的人提供一个替代方案。

(请注意, 对于那些使用新 fetch 的 api, 角度或承诺, 我已经添加了另一个答案下面)


你所面临的

这是另一个答案中的 "问题解释" 的简短摘要, 如果你看完这篇文章后不确定, 请阅读。

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

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

这里有一个简单的类比

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

(Fiddle)

返回的值 aundefined 因为 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" 到屏幕上。(菲德尔)

可能的解决方案

基本上有两种方法可以解决此问题:

  1. 使 AJAX 调用同步 (让我们调用它 SJAX)。
  2. 重新构造代码以正常使用回调。

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

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

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

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

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

第3个答案

Xmlhttprequest 2 (首先阅读Benjamin Gruenbaum & 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. 它使用新的加载 (因此您不必检查状态和状态)
  4. 还有一些其他的情况, 我不记得, 使 XMLHttpRequest 1 烦人。

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

最简单的:

this.response

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

e.target.response

例子:

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

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

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

没有比这更容易的了

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

查看Xmlhttprequest 高级功能

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

只有当您想要获取状态 2 上的标头时, 才会使用状态时。

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


现在, 如果您想要使用发布和 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);

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

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


正如评论中提到的, 错误和同步的使用确实完全打破了答案的意义。哪种方法是以正确的方式使用 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);

在上面的脚本中, 您有一个静态定义的错误处理程序, 因此它执行 n

第4个答案

如果你使用的是承诺, 这个答案是给你的。

这意味着 AngularJS、jQuery (延迟)、原生 XHR 的替换 (提取)、EmberJS、BackboneJS 的保存或返回承诺的任何节点库。

您的代码应该是这样的:

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

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

费利克斯·克林在为使用 JQUERY 和 AJAX 回调的人写答案方面做得很好。我对原生 XHR 有一个答案。这个答案是用于在前端或后端的承诺的通用用法。


核心问题

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

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

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

下面是该问题的一个简单类比:

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

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

由于操作尚未发生 (AJAX、服务器调用、io、计时器), 因此您需要在请求有机会告诉代码该值是什么之前返回该值。

解决此问题的一个可能的方法是重新编程, 告诉程序在计算完成后要执行的操作。承诺通过具有时间 (时间敏感性) 的性质积极促成这一点。

快速重述承诺

承诺是一个随着时间的推移而产生的价值。承诺有状态, 它们开始时没有任何价值, 可以满足于:

  • 满足了计算成功完成的意义。
  • 拒绝了计算失败的含义。

一个承诺只能改变一次状态 , 之后它将永远保持在同一个状态。您可以将 then 处理程序附加到承诺中, 以提取它们的值并处理错误. then 承诺是通过使用返回它们的 Api创建的。例如, 更现代的 AJAX 替换 fetch 或 jQuery 的 $.get 返回承诺。

当我们 .then 呼吁一个承诺并从中返回一些东西时--我们得到了对加工价值的承诺。如果我们再兑现承诺, 我们会得到惊人的东西, 但让我们守住我们的马。

带着承诺

让我们看看如何用承诺来解决上述问题。首先, 让我们从上面展示我们对承诺状态的理解, 方法是使用[](https://developer.mozilla.org/en-US/docs/Mozi href=)

第5个答案

您使用 Ajax 的情况不正确。这样做的目的不是让它返回任何东西, 而是将数据传递给一个被称为回调函数的东西, 它处理数据。

那是:

function handleData( responseData ) {

    // Do what you want with the data
    console.log(responseData);
}

$.ajax({
    url: "hi.php",
    ...
    success: function ( data, status, XHR ) {
        handleData(data);
    }
});

返回提交处理程序中的任何内容都不会执行任何操作。相反, 您必须将数据传递出去, 或者直接在成功函数中使用它执行所需操作。

第6个答案

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

我会回答一个可怕的前瞻, 手绘漫画。第二个图像是 result undefined 原因是在您的代码示例中。

enter image description here

第8个答案

安古拉拉 1

对于使用Angularjs的人, 可以使用这种情况 Promises

在这里, 它说,

承诺可用于解锁异步函数, 并允许将多个函数链接在一起。

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

下面提到的文档中的示例。

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

 // promiseB will be resolved immediately after promiseA is resolved
 // and its value will be the result of promiseA incremented by 1.

安古拉尔 2 及更高版本

Angular2 下面的示例中, 建议与之一起使用 Observables Angular2

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

}

你可以用这种方式消耗它

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

请看这里的原始帖子。但是, 类型脚本不支持本机 Es6 承诺, 如果你想使用它, 你可能需要插件。

此外, 这里是这里定义的承诺规范

第9个答案

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

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

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

例子:

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

(我们可以取消 expecting , 只是使用 results.length === theArray.length , 但这让我们可以选择的可能性, 而电话 theArray 是未完成的...)

请注意, 即使结果不按 index forEach 顺序到达 (因为 results 异步调用不一定按启动的顺序完成), 我们也要使用从将结果保存在与它相关的条目相同的位置。

但是, 如果你需要返回这些结果

第10个答案

请看下面的示例:

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 请求完成, 然后执行控制台. log (resr. 工友) (作为正常的异步流)。

这是 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);
  });
})();
第11个答案

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

下面是相同的示例:

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 该对象在异步操作过程中存储值。这样, 即使在异步作业之后, 也可以使用结果。

我经常使用这种方法。我很想知道这种方法在通过连续模块将结果布线回的情况下的工作原理如何。

第12个答案

这是在许多新的 JavaScript 框架中使用的数据绑定将极大地适合您的两种方式之一..。

所以, 如果你使用的角度, 反应或任何其他框架, 做两种方式的数据绑定,这个问题只是为你解决, 所以在简单的词 undefined , 你的结果是在第一阶段, 所以你已经得到之前, 你收到 result = undefined 的数据, 然后只要你 g 在结果, 它将更新并被分配到新的值, 这是您的 Ajax 调用的响应..。

但是, 如何在纯javascriptjquery中执行此操作, 例如, 正如您在本问题中所问的那样?

您可以使用回调承诺和最近可观察的方法为您处理它, 例如, 在承诺中, 我们有一些函数, 如成功 () 或然后 (), 将在您的数据为您准备好时执行, 回调或观察到订阅函数。

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

有关承诺可观察的更多信息研究, 这些承诺和可观察的方法是做这种新东西的新方法。

第13个答案

虽然承诺和回调在许多情况下效果很好, 但表达类似这样的东西是痛苦的。

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
}

您可以在此处签出项目。

第14个答案

简短的回答是,您必须实现如下所示的回调:

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

$.ajax({
    url: "...",
    success: callback
});
第15个答案

下面的示例, 我已经写了演示如何

  • 处理异步 HTTP 调用;
  • 等待来自每个 API 调用的响应;
  • 使用承诺模式;
  • 使用活动.

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

上下文。该示例正在查询Spotify WEB api终结点, 以便 playlist 搜索给定查询字符串集的对象:

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

对于每个项目, 一个新的承诺将触发一个块- ExecutionBlock -分析结果, 根据结果数组安排一组新的承诺, 即 spotify user 对象列表, 并在异步中执行新的 http 调用 ExecutionProfileBlock

然后, 您可以看到一个嵌套的 "承诺" 结构, 该结构允许您生成多个完全异步嵌套的 HTTP 调用, 并通过每个调用子集加入 Promise.all 结果。

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

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

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

var spotifyAccessToken = "YourSpotifyAccessToken";
var console = {
    log: function(s) {
        document.getElementById("console").innerHTML += s + ""
    }
}

// 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);
}}
第16个答案

2017 年答案: 您现在可以在每个当前浏览器和节点中完全按照自己的要求执行操作

这很简单:

  • 返回承诺
  • 使用"等待",它将告诉 JavaScript 等待将其解析为一个值的承诺 (如 http 响应)
  • 将 "异步"关键字添加到父函数

下面是您的代码的工作版本:

(async function(){

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

})()

等待是支持在所有当前浏览器和节点 8

第17个答案

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

function $http(apiConfig) {
    return new Promise(function (resolve, reject) {
        var client = new XMLHttpRequest();
        client.open(apiConfig.method, apiConfig.url);
        client.send();
        client.onload = function () {
            < 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)
});
第18个答案

J 是一个单线程。

浏览器可分为三个部分:

1)Event 环路

2)Web API

3)Event 队列

事件循环永远运行, 即一种无限循环。事件队列是将您的所有函数推送到某些事件 (例如: click) 的位置, 这是一个接一个执行的队列, 并放入事件循环, 执行此函数, 并为第一个执行后的下一个函数做好准备。这意味着, 在队列中的函数在事件循环中执行之前, 一个函数的执行不会启动。

现在让我们假设我们在队列中推送了两个函数, 一个函数是为了从服务器获取数据, 另一个函数是利用这些数据。我们首先在队列中推送了 serverRequest () 函数, 然后使用了 ServerRequest () 函数。serverRequest 函数在事件循环中运行, 并对服务器进行调用, 因为我们永远不知道从服务器获取数据需要多少时间, 因此这个过程预计需要时间, 因此我们忙于我们的事件循环, 从而挂起我们的页面, 这就是 Web API 发挥作用的地方, 它从事件循环中获取此函数, 并处理服务器创建事件循环的自由问题, 以便我们可以从队列中执行下一个函数。队列中的下一个函数是使用数据 (), 它是循环的, 但由于没有可用的数据, 它会被浪费, 下一个函数的执行将一直持续到队列结束。(这就是所谓的异步调用, 即我们可以做一些其他的事情, 直到我们得到数据)

假设我们的 serverRequest () 函数在代码中有一个返回语句, 当我们从服务器获取数据时, 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

第19个答案

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

如果基础函数被承诺

nsynjs 将按顺序评估所有承诺, 并将承诺结果放入 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);};

如果基础函数未承诺

步骤 1。将回调到 nsynj 感知包装器中的换行函数 (如果它具有可推广的版本, 则可以跳过此步骤):

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

第20个答案

这是我们在与 JavaScript 的 "谜团" 作斗争时面临的一个非常普遍的问题。今天让我试着揭开这个谜团的神秘面纱。

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

function foo(){
// do something
 return 'wohoo';
}

let bar = foo(); // bar is 'wohoo' here

这是一个简单的同步函数调用 (每行代码在下一个序列之前都 "完成了它的工作"), 结果与预期的相同。

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

function foo(){
 setTimeout( ()=>{
   return 'wohoo';
  }, 1000 )
}

let bar = foo() // bar is undefined here

所以你去吧, 那个延迟只是打破了我们预期的功能!但到底发生了什么?嗯, 如果你看一下代码, 其实是很符合逻辑的。函数 foo() , 在执行时, 返回什么 (因此返回值 undefined 是), 但它确实启动一个计时器, 它执行一个函数后, 1 s 返回 "wohoo"。但正如您所看到的, 分配给条形图的值是立即从 foo () 返回的内容, 而不是以后会出现的任何其他内容。

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

让我们向我们的函数请求一个承诺。 承诺实际上是关于它的含义: 它意味着该函数保证您提供它在未来获得的任何输出。所以让我们看看它在行动中, 我们的小问题上面:

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

更新 (与异步等待的承诺)

除了使用 then/catch 与承诺合作外, 还有一种方法。这样做的目的是识别异步函数, 然后等待承诺得到解决, 然后再移动到下一行代码。这仍然只是 promises 引擎盖下, 但有不同的句法方法。为了使事情更清楚, 您可以找到下面的比较:

抓拍版本:

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

等待版本:

  async function fetchUsers(){
     try{
        let users = await getUsers()
        return users;
     }
     catch(err){
        throw err;
     }
  }
第21个答案

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

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

示例: jQuery 延迟实现可处理多个请求

var App = App || {};

App = {
    getDataFromServer: function(){
  var self = this,
             deferred = $.Deferred(),
             requests = [];

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

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

init: function(){

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

       // Do the operations which you wanted to do when you
       // get a response from Ajax, for example, log response.
    }, this));
}
第22个答案

ECMAScript 6 具有 "生成器", 可让您轻松地使用异步样式编程。

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

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

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

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

如果您需要针对不支持 ES6 的浏览器, 您可以通过 Babel 或闭编译器运行代码来生成 ECMAScript 5。

回调 ...args 被包装在数组中, 并在读取时进行结构化, 以便模式可以处理具有多个参数的回调。例如, 节点fs:

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

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();
第24个答案

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

第25个答案

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

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

以一个例子为例。你打电话给送牛奶的人, 点一些牛奶。当它来的时候, 你想把它放在你的咖啡里。你现在不能把牛奶放进咖啡里, 因为它还没有到。你必须等它来, 然后再把它放进你的咖啡里。换句话说, 以下方法不起作用:

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

因为 JS 无法知道它需要等待 order_milk 才能完成, 然后才能执行 put_in_coffee 。换句话说, 它不知道 order_milk 这是异步的--是未来某个时候才会产生牛奶的东西。JS 和其他声明性语言无需等待即可执行一个又一个语句。

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

order_milk(put_in_coffee);

order_milk启动, 订购牛奶, 然后, 当它到达时, 它调用 put_in_coffee

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

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

在那里, 我传递到牛奶放 put_in_coffee 在它, 也执行 drink_coffee 的行动 (), 一旦牛奶已经放进去。这样的代码变得很难编写、读取和调试。

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

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

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

输入承诺

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

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

order_milk() . then(put_in_coffee)

这样做的一个优点是, 我们可以将这些串在一起, 以创建未来发生的序列 ("chainin

第26个答案

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

function foo() {
    var result;

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

    return result;
}

function myCallback(response) {
    // Does something.
}
第27个答案

问题是:

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

它可以被解释为:

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

解决方法是避免回调, 并使用承诺和**异步等待的**组合。

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

(虽然它可以用 Javascript 编写, 但我更喜欢用 Python 编写, 并使用Tranc 容特将其编译为 Javascript。这将是足够清楚。

让我们首先启用 JQuery 用法, 以 $ 以下方式提供 S :

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

定义一个返回 "承诺"的函数, 在这种情况下, 是 ajax 调用:

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

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

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

使用 ES2017, 您应该将此作为函数声明

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

像这样处决它。

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

或者是 "承诺" 语法

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

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

})
第29个答案

让我们先看看森林, 然后再看树木。

这里有很多内容丰富的答案, 有很好的细节, 我不会重复其中的任何一个。在 JavaScript 中编程的关键是首先要有正确的整体执行心理模型

  1. 您的入口点作为事件的结果执行。例如, 包含代码的脚本标记加载到浏览器中。 (因此, 这就是为什么您可能需要关注页面是否愿意运行您的代码, 如果它需要首先构造 dom 元素, 等等。
  2. 您的代码执行到完成 (无论它执行多少异步调用), 而无需执行任何回调, 包括 xhr 请求、设置超时、dom 事件处理程序等。等待执行的每个回调都将位于队列中, 等待轮到它们在触发的其他事件都完成执行后运行。
  3. 每次对 XHR 请求的回调, 设置超时或一旦调用该事件的 dom, 就会运行到完成。

好消息是, 如果你很好地理解这一点, 你就永远不用担心比赛条件了。首先, 您应该如何组织代码, 将其作为对不同离散事件的基本响应, 以及如何将它们组合到逻辑序列中。 您可以使用承诺或更高级别的新 asyncn 等待作为实现此目的的工具, 也可以使用自己的工具。

但你不应该使用任何战术工具来解决问题, 直到你对实际的问题领域感到满意。绘制这些依赖项的映射, 以了解何时需要运行什么。尝试对所有这些回调采取临时方法对您没有好处。

第30个答案

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

事件循环和并发模型

有三件事你需要注意;队列;事件循环和堆栈

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

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

一旦它收到一条消息来运行某样东西, 它就会将其添加到队列中。队列是等待执行的内容的列表 (如 ajax 请求)。想象一下, 就像这样:

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

当其中一个消息要执行时, 它将从队列中弹出消息并创建一个堆栈, 堆栈是 JS 执行消息中的指令所需执行的所有操作。因此, 在我们的例子, 它被告知调用foobarFunc

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

因此, foobarFunc 需要执行的任何操作 (在我们的示例 anotherFunction 中) 都将被推送到堆栈上. 执行, 然后忘记--然后事件循环将移动到队列中的下一件事情上 (或侦听消息)

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

当什么东西要运行

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

最大的问题是什么时候才能得到回应?答案是我们不知道--所以事件循环正在等待该消息说 "嘿, 运行我"。如果 JS 只是同步等待该消息, 你的应用将冻结, 它将吸吮。因此, JS 继续执行队列中的下一项, 同时等待消息被添加回队列。

这就是为什么使用异步功能, 我们使用称为回调的东西。这简直是一个承诺。和在一起, 我保证在某个时候返回一些东西, jquery 使用调用 deffered.done deffered.faildeffered.always (除其他外) 的特定回调。你可以在这里看到他们

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

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

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

所以大多数时候 (但并不总是) 你 foo 会通过不foo()

希望这将有一定的意义。当你遇到这样的事情, 似乎令人困惑-我强烈建议阅读文档充分, 至少要了解它。这将使你成为一个更好的开发人员。

相关问题

如何从异步调用返回响应? 循环中的 JavaScript 闭包--简单的实际示例 如何从异步调用返回响应? 触发点击事件在 href 上动态添加链接 为什么在函数内部修改变量后, 它没有改变?-异步代码引用 如何在 JavaScript 中执行异步函数?