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

我有一个函数 foo ,这是一个Ajax请求。我应该如何从异步调用返回响应呢?

我尝试从成功回调中返回值,并将响应分配给函数内部的局部变量,并返回该函数,但没有一种方法实际返回响应。

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

解答

说明问题

Ajax中的A代表异步。 这意味着发送请求(或者接收响应)是从正常执行流程中删除的。 在你的例子中,$ .ajax立即返回,下一条语句返回结果;在被传递的函数之前执行,成功回调甚至被调用。

这是一个有希望使同步和异步流之间的差异更清晰的类比:

同步:

想像一下,你打给一个朋友打来电话,请他给你看一些东西。 虽然可能需要一段时间,你等待电话,盯着太空,直到你的朋友给你需要的答案。

当您进行包含“正常”代码的函数调用时,也会发生这种情况:

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的执行时间有上限,浏览器会询问用户是否继续执行。

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

重组代码:

让函数接受回调

更好的方法是在回调之前组织好你的代码。 在这个问题的例子中,你可以使foo接受回调,并将其用作成功回调。 所以这

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

变成

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

Here we pass a function as argument to foo. You can pass any function reference, for example:

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

foo 的定义如下:

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

回调函数将引用我们传递给foo的函数,当我们调用它时,我们将其传递给成功。 即 一旦Ajax请求成功,$ .ajax将调用回调并将响应传递给回调(可以使用结果参考),因为这是我们如何定义回调)。

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

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

使用回调编写代码比看起来更容易。 毕竟,浏览器中的JavaScript是大量的事件驱动(DOM事件)。 接收Ajax响应只不过是事件。

当您必须使用第三方代码时,可能会出现困难,但大多数问题可以通过考虑应用程序流程来解决。


使用promises:

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

Promises接收两个值,当promise收到值(已解决)或取消(拒绝)时,它通知所有想要访问该值的“侦听器”。

与普通回调相比的优点是它们允许您将代码解耦,并且它们更容易组合。

下面是使用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提供的所有优点超出了这个答案的范围,但是如果你编写新的代码,你应该认真考虑它们。它们提供了很好的抽象和分离代码。

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


本文由 w3cmark_前端笔记 版权所有,转载时请注明出处。
注明出处格式:w3cmark (http://www.w3cmark.com/2017/561.html)

分享到:

关注w3cmark
微信公众号 w3cmark_com