问题

我有一个函数 foo ,它产生一个Ajax请求.如何从 foo ?

返回响应

我试图从 success 回调中返回值,以及将响应分配给函数内的局部变量,并返回一个,但是这些方法都没有返回响应. p>

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


解决方法

-> For a more general explanation of async behavior with different examples, please see Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference

-> If you already understand the problem, skip to the possible solutions below.

Explanation of the problem

Ajax中的 A 代表异步.这意味着发送请求(或者接收响应)被从正常执行流中取出.在你的例子中, $.ajax 会立即返回,并且下一个语句 return result; 在你作为 success 甚至叫.

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

Synchronous

想象一下,你打电话给朋友,请他为你找点东西.虽然可能需要一段时间,您可以在手机上等待,直到您的朋友给您所需的答案.

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

function findItem() {
    var item;
    while(item_not_found) {
        // search
    }
    return item;
}

var item = findItem();

// Do something with item
doSomethingElse();

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

Asynchronous

由于同样的原因,您再次呼叫您的朋友.但是,这一次你告诉他,你是在赶时间,他应该的给你回电话的手机上.挂断电话后,离开家,做你打算做什么.一旦你的朋友叫你回来,你正在处理他给你的信息.

这就是当你做Ajax请求时发生的情况.

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

代替等待响应,执行将立即继续,并且执行Ajax调用后的语句.要最终获得响应,您需要提供一个在收到响应后调用的函数,回调(通知某事?回拨?在调用回调之前执行调用之后的任何语句.


Solution(s)

拥抱JavaScript的异步本质.虽然某些异步操作提供了同步对象("Ajax"),但通常不建议使用它们,特别是在浏览器上下文中.

为什么会这样呢?

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

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

Restructure code

Let functions accept callbacks

更好的方法是在回调周围正确地组织代码.在问题的示例中,您可以使 foo 接受回调,并将其用作 success 回调.所以这

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

变为

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

这里我们将一个函数作为参数传递给 foo .您可以传递任何函数引用,例如:

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

foo(myCallback);

foo 本身定义如下:

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

callback 将引用我们传递给 foo 的函数,我们只需将它传递给 success .也就是说一旦Ajax请求成功, $.ajax 将调用回调并将响应传递给回调(可以用 result 因为这是我们如何定义回调).

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

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

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

Use promises

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

Promises是未来值的容器.当promise接收到值(被解析)或被取消(被拒绝)时,它会通知所有想要访问此值的"监听器".< / p>

与普通回调相比,它的优点是它们允许你解耦你的代码,并且它们更容易编写.

这是一个使用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调用,我们可以使用这样的promise:

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

描述promise提供的所有优点超出了这个答案的范围,但如果你编写新的代码,你应该认真考虑它们.它们提供了伟大的代码抽象和分离.

有关promises的详情: HTML5 rocks - JavaScript Promise

jQuery: Use deferred objects

延迟对象是jQuery的promise的自定义实现(在Promise API标准化之前).它们几乎像promise,但暴露一个稍微不同的API.

jQuery的每个Ajax方法都已经返回了一个"deferred对象"(实际上是一个deferred对象的promise),你可以从你的函数返回:

function ajax() {
    return $.ajax(...);
}

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

Promise gotchas

请记住,promise和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
});

现在我们仍然在服务器上调用'/ password'页面,但是我们的代码现在正确处理服务器响应的等待时间. $.ajax()调用仍然立即返回一个jQuery Ajax Deferred对象,但我们使用它来附加事件侦听器到 .done() .fail ().在 .done()调用中,服务器响应正常响应(HTTP 200),我们检查服务器返回的对象.在这个例子中,服务器只是返回true,如果登录成功,false如果没有,所以 if(r)检查true / false.

.fail()处理程序中,我们正在处理一些错误 - 例如,如果用户在输入用户名和密码时丢失了互联网连接,下来.


Not recommended: Synchronous "Ajax" calls

正如我所提到的,一些异步操作具有同步对等体.我不主张他们的使用,但为了完整性,这里是如何执行同步调用:

Without jQuery

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

jQuery

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

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

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

提起!无法进行同步 JSONP 请求. JSONP本质上总是异步的(另一个原因,甚至不考虑这个选项).




相关问题推荐