javascript - js等待异步完成 - 如何从异步调用返回响应?




js异步返回 (20)

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

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

问题

AjaxA代表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调用之后的语句。 为了最终得到响应,你提供了一个在收到响应后调用的函数,一个回调 (注意一些事情? 回调 ?)。 调用之后的任何语句都会在调用回调之前执行。

解决方案(S)

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

你问为什么这么糟糕?

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

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

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

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

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

ES2017 +:与async/await承诺

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

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

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

您可以在MDN上阅读有关async/awaitawait更多信息。

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

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

// Async functions always return a promise
getAllBooks()
  .then(function(books) {
    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()

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

承诺是未来价值观的容器。 当promise接收到值(已解决 )或取消( 拒绝 )时,它会通知所有想要访问此值的“侦听器”。

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

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

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

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

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

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

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

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

关于promises的更多信息: HTML5 rocks - JavaScript Promises

旁注:jQuery的延迟对象

延迟对象是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()在检查服务器上的'/ 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对象, .open false作为第三个参数传递给.open

jQuery的

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

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

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

当心! 无法生成同步JSONP请求。 JSONP本质上总是异步的(甚至不考虑这个选项的另一个原因)。

https://code.i-harness.com

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

Js是单线程的。

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

1)事件循环

2)Web API

3)事件队列

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

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

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

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

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

 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://.com/a/14220323/7579856


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

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

你面对的是什么

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

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

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

这是一个简单的比喻

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

(Fiddle)

返回的值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)

可能的解决方案

基本上有两种解决方法:

  1. 使AJAX调用同步(让我们称之为SJAX)。
  2. 重构代码以使用回调正常工作。

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

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

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

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

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

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上的AJAX入门指南


如果您使用承诺,这个答案适合您。

这意味着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处理程序总是异步执行 - 也就是说, 它们下面的代码不在.then处理程序之后。

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

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

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

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

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

快速回顾承诺

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

  • 实现了计算成功完成的意义。
  • 拒绝意味着计算失败。

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

当我们在承诺上调用。然后从承诺中返回一些东西时 - 我们得到了对已处理值的承诺。 如果我们回到另一个承诺,我们会得到惊人的东西,但让我们抓住我们的马。

有了承诺

让我们看看我们如何用promises解决上述问题。 首先,让我们通过使用Promise构造函数来创建延迟函数来演示我们对上面的promise状态的理解:

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

基本上,我们不是返回一个因为并发模型而无法做到的 - 而是返回一个我们可以包的值的包装器 。 它就像一个你可以打开的盒子。

应用这个

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

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

这个有点棘手但非常强大的技巧让我们以同步的方式编写异步代码。有几个“跑步者”为你做这个,写一个是几行代码,但超出了这个答案的范围。我将在Promise.coroutine这里使用Bluebird ,但还有其他包装,如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保证。通过添加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
}

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


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

  1. then()
  2. Q - JavaScript的承诺库
  3. A + Promises.js
  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));
    }
};
App.init();


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. 它使用新的onload(因此您不必检查readystate && status)
  4. 还有一些我不记得的情况使得XMLHttpRequest 1令人讨厌。

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

最简单的:

this.response

或者,如果由于某种原因你bind()回调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高级功能

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

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

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

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

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

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

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

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

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

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

只有this.type设置为error。

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

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

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

同步读取文件

不要那样做。

如果你想阻止浏览器一段时间加载一个漂亮的大txt文件同步。

function omg(a, c){ // URL
  c = new XMLHttpRequest;
  c.open('GET', a, true);
  c.send();
  return c; // Or c.response
}

现在你可以做到

 var res = omg('thisIsGonnaBlockThePage.txt');

没有其他方法可以以非异步方式执行此操作。 (是的,使用setTimeout循环......但是认真吗?)

另一点是......如果您使用API​​或只是您拥有列表的文件或任何您总是为每个请求使用不同的函数...

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

以上功能仅供基本使用。

如果你想扩展功能......

是的你可以。

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

但是你可以用XMLHttpRequest 2做很多事情:

我制作了一个下载管理器(使用简历,文件读取器,文件系统两侧的范围),使用画布的各种图像缩放器转换器,使用base64images填充websql数据库等等......但在这些情况下,您应该仅为此目的创建一个函数...有时你需要一个blob,数组缓冲区,你可以设置标题,覆盖mimetype,还有更多......

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


看看这个例子:

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/


简短回答:您的foo()方法立即返回,而在函数返回后$ajax()调用异步执行。问题是,一旦它返回,存储异步调用检索的结果的方式或位置。

该线程中给出了几种解决方案。也许最简单的方法是将对象传递给foo()方法,并在异步调用完成后将结果存储在该对象的成员中。

function foo(result) {
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;   // Store the async result
        }
    });
}

var result = { response: null };   // Object to hold the async result
foo(result);                       // Returns before the async completes

请注意,调用foo()仍将返回任何有用的内容。但是,现在将存储异步调用的结果result.response


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

这是一个相同的例子:

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

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


使用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)

})

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

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

这个工作示例是独立的。它将定义一个使用window 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 searchAPI将要求在请求标头中指定访问令牌:

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

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

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

// Simple XMLHttpRequest
// based on https://davidwalsh.name/xmlhttprequest
SimpleRequest = {
    call: function(what, response) {
        var request;
        if (window.XMLHttpRequest) { // Mozilla, Safari, ...
            request = new XMLHttpRequest();
        } else if (window.ActiveXObject) { // Internet Explorer
            try {
                request = new ActiveXObject('Msxml2.XMLHTTP');
            }
            catch (e) {
                try {
                  request = new ActiveXObject('Microsoft.XMLHTTP');
                } catch (e) {}
            }
        }

        // State changes
        request.onreadystatechange = function() {
            if (request.readyState === 4) { // Done
                if (request.status === 200) { // Complete
                    response(request.responseText)
                }
                else
                    response();
            }
        }
        request.open('GET', what, true);
        request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken);
        request.send(null);
    }
}

//PromiseAll
var promiseAll = function(items, block, done, fail) {
    var self = this;
    var promises = [],
                   index = 0;
    items.forEach(function(item) {
        promises.push(function(item, i) {
            return new Promise(function(resolve, reject) {
                if (block) {
                    block.apply(this, [item, index, resolve, reject]);
                }
            });
        }(item, ++index))
    });
    Promise.all(promises).then(function AcceptHandler(results) {
        if (done) done(results);
    }, function ErrorHandler(error) {
        if (fail) fail(error);
    });
}; //promiseAll

// LP: deferred execution block
var ExecutionBlock = function(item, index, resolve, reject) {
    var url = "https://api.spotify.com/v1/"
    url += item;
    console.log( url )
    SimpleRequest.call(url, function(result) {
        if (result) {

            var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) {
                return item.owner.href;
            })
            resolve(profileUrls);
        }
        else {
            reject(new Error("call error"));
        }
    })
}

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

promiseAll(arr, function(item, index, resolve, reject) {
    console.log("Making request [" + index + "]")
    ExecutionBlock(item, index, resolve, reject);
}, function(results) { // Aggregated results

    console.log("All profiles received " + results.length);
    //console.log(JSON.stringify(results[0], null, 2));

    ///// promiseall again

    var ExecutionProfileBlock = function(item, index, resolve, reject) {
        SimpleRequest.call(item, function(result) {
            if (result) {
                var obj = JSON.parse(result);
                resolve({
                    name: obj.display_name,
                    followers: obj.followers.total,
                    url: obj.href
                });
            } //result
        })
    } //ExecutionProfileBlock

    promiseAll(results[0], function(item, index, resolve, reject) {
        //console.log("Making request [" + index + "] " + item)
        ExecutionProfileBlock(item, index, resolve, reject);
    }, function(results) { // aggregated results
        console.log("All response received " + results.length);
        console.log(JSON.stringify(results, null, 2));
    }

    , function(error) { // Error
        console.log(error);
    })

    /////

  },
  function(error) { // Error
      console.log(error);
  });
<div id="console" />

here广泛讨论了这个解决方案。


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

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结帐项目。


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

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

如果潜在的功能被宣传

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

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/exampleshttps://github.com/amaksr/nsynjs/tree/master/examples


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

function foo() {
    var result;

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

    return result;
}

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

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

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


我会回答一个看起来很可怕的手绘漫画。第二图像是为什么的原因resultundefined在你的代码示例。


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

而不是向你抛出代码,有两个概念是理解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继续执行队列中的下一个项目,同时等待消息被添加回队列。

这就是为什么使用异步功能我们使用称为回调的东西。它实际上就像一个then()。正如我承诺在某些时候返回一些东西, jQuery使用调用的特定回调deffered.done deffered.faildeffered.always(以及其他)。你可以here看到它们here

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

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

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

所以大部分的时间(但不总是),你会通过foofoo()

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


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

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

但是你如何在纯javascriptjQuery中做到这一点,例如你在这个问题中提到的?

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

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

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

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

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

有关promisesobservables的更多信息,这些是更新的方法来执行此异步操作。







ecmascript-2017