如何从异步调用返回响应? [英] How do I return the response from an asynchronous call?

查看:275
本文介绍了如何从异步调用返回响应?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个函数 foo ,这是一个Ajax请求。我如何从 foo 中返回答复?



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

  function foo(){
var result;

$ .ajax({
url:'...',
success:function(response){
result = response;
//返回响应; //< - 我尝试了一个
}
});

返回结果;
}

var result = foo(); //它总是结束为'undefined`。


解决方案


code> - > 有关具有不同示例的异步行为的更一般说明,请参阅 为什么我的变量在修改函数之后是不变的? - 异步代码参考



- > 如果您已经了解问题,请跳过




问题



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



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



同步



想象一下,你打给一个朋友打个电话,您。虽然可能需要一段时间,你等待电话,盯着太空,直到你的朋友给你所需的答案。



同样的事情发生在你做一个包含正常代码的函数调用:

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

var item = findItem();

//用item
doSomethingElse()执行某些操作;

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



异步



同样的原因你再次打电话给你的朋友。但是这次你告诉他你很匆忙,他应该在你的手机上打电话给你。你挂起来,离开房子,做任何你打算做的事情。一旦你的朋友打电话给你,你正在处理他给你的信息。



这是Ajax请求发生的时候。

  findItem(function(item){
//执行项目
}
doSomethingElse();

代替等待响应,执行将立即继续执行,Ajax调用后的语句将被执行。为了最终得到响应,您提供了一旦收到响应后调用的函数,一个回调(注意某些?回调?)。在该呼叫之前的任何声明在调用回调之前执行。









解决方案



拥有JavaScript的异步性质!虽然某些异步操作提供同步对等(Ajax也是如此),通常不鼓励使用它们,特别是在浏览器环境中。



你为什么这么糟?



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



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



在下面我们将看看三个不同的解决方案,这些解决方案都建立在彼此之间: p>


  • 承诺与 async /等待 (ES2017 +,可用旧版浏览器,如果您使用的是转换器或再生器)

  • 回调(流行于节点)

  • c $ c> (ES2015 +,如果您使用许多承诺库之一,可在旧版浏览器中使用)



目前的浏览器和节点7 +都有三种。






ES2017 +:通过 <$ c承诺$ c> async / await



2017年发布的新ECMAScript版本引入了语法级支持用于异步功能蒸发散。在 async 等待的帮助下,您可以使用同步样式来编写异步。不要误会:代码仍然是异步的,但是更容易阅读/理解。



async / await 建立在承诺之上:一个 async 函数总是返回一个承诺。 等待展开一个承诺,并且导致承诺解决的价值,或者如果承诺被拒绝,则会引发错误。



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



您可以阅读有关 async 等待 在MDN上。



这是一个以上延迟为基础的例子:

  //使用'superagent'这将返回一个承诺。 
var superagent = require('superagent')

//这不是声明为`async`,因为它已经返回承诺
function delay(){
//`delay`返回一个promise
return new Promise(function(resolve,reject){
//只有`delay`能够解决或拒绝承诺
setTimeout (){
resolve(42); // 3秒钟后,解析值为42
},3000);
});
}


异步函数getAllBooks(){
try {
//获取当前用户的图书ID列表
var bookIDs = await superagent.get('/ user / books');
//等待一秒(仅为了这个例子)
等待延迟(1000);
//获取有关每本书的信息
return await superagent.get('/ books / ids ='+ JSON.stringify(bookIDs));
} catch(error){
//如果任何等待的承诺被拒绝,这个catch块
//将捕获拒绝原因
返回null;
}
}

// Async函数总是返回一个承诺
getAllBooks()
.then(function(books){
console .log(书);
});

较新的浏览器节点版本支持 async / await 。您还可以通过再生器(或使用再生器的工具)的帮助将代码转换为ES5来支持较旧的环境,例如 Babel )。






让函数接受回调



回调只是传递给另一个函数的函数。那个其他函数可以调用在准备就绪时传递的函数。在异步进程的上下文中,每当异步进程完成时,将调用回调函数。通常结果将传递给回调。



在问题的示例中,您可以使 foo 接受回调,并将其用作成功回调。所以这个

  var result = foo(); 
//依赖于'result'的代码

成为

  foo(function(result){
//依赖于'result'的代码
});

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

  function myCallback(result){
//依赖于'result'的代码
}

foo(myCallback);

foo 本身定义如下: / p>

  function foo(callback){
$ .ajax({
// ...
成功:回调
});
}

回调将引用对于我们传递给 foo 的函数,我们简单地将其传递给 success 。即一旦Ajax请求成功, $。ajax 将调用回调并将响应传递给回调(可以是引用 result ,因为这是我们如何定义回调)。



您还可以处理响应将其传递给回调:

  function foo(callback){
$ .ajax({
/ / ...
success:function(response){
//例如,过滤响应
callback(filtered_response);
}
});
}

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

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






ES2015 +:承诺与 then()



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



承诺是未来值的容器。当承诺收到价值(它是解决)或取消(拒绝)时,它通知所有想要访问该值的监听者。 / p>

优于普通回调的优点在于它们允许您脱离代码,并且更容易撰写。



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

  function delay(){
//`delay`返回promise
return new Promise(function(resolve,reject){
//只有`delay`能够解决或拒绝承诺
setTimeout(function(){
resolve( 42); // 3秒钟后,解析值为42
},3000);
});
}

delay()
.then(function(v){//`delay`返回承诺
console.log(v); //日志一旦解决了这个价值
})
.catch(function(v){
//或者做一些其他如果被拒绝
//(它不会发生在这个例子,因为`reject`不被调用)
});

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

 函数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){
//代码取决于结果
})
.catch(function(){
//发生错误
});

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



有关承诺的更多信息: HTML5岩石 - JavaScript承诺



旁注:jQuery的延迟对象



延迟对象是jQuery的承诺的自定义实现(Promise API之前是标准化)。他们的行为几乎像承诺,但暴露了一个稍微不同的API。



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

 函数ajax(){
return $ .ajax(...) ;
}

ajax()。done(function(result){
//代码取决于结果
})。fail(function(){
//发生错误
});



旁注:承诺的困扰



保留记住,承诺和延迟对象只是对于未来价值的容器,它们不是值本身。例如,假设您有以下内容:

  function checkPassword(){
return $ .ajax({
url:'/ password',
data:{
username:$('#username')。val(),
password:$('#password' )
},
类型:'POST',
dataType:'json'
});
}

if(checkPassword()){
//告诉他们登录的用户
}

此代码误解了上述异步问题。具体来说, $。ajax()在检查服务器上的/ password页面时不冻结代码,它会向服务器发送请求,同时等待,立即返回一个jQuery Ajax Deferred对象,而不是来自服务器的响应。这意味着如果语句将始终获取此Deferred对象,将其视为 true ,然后继续执行用户登录不好。



但修复很简单:

  checkPassword()
.done(function(r){
if(r){
//告诉他们登录的用户
} else {
//告诉用户他们的密码不好
}
})
.fail(function(x){
//告诉用户发生了不良事件
});









不推荐:同步Ajax调用



正如我所提到的,一些(!)异步操作具有同步对等体。我不主张使用它,但为了完整起见,这里是如何执行同步调用:



没有jQuery



如果您直接使用 XMLHTTPRequest 对象,将 false 作为 .open



jQuery < h3>

如果您使用 jQuery ,您可以将 async 选项设置为 false 。请注意,自jQuery 1.8起,此选项不推荐使用。
然后您可以使用成功回调或访问 responseText http://api.jquery.com/jQuery.ajax/#jqXHR =noreferrer> jqXHR对象

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

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



头up!不可能同步 JSONP 请求。 JSONP的性质总是异步的(再一个原因甚至不考虑这个选项)。


I have a function foo which makes an Ajax request. How can I return the response from foo?

I tried to return the value from the success callback as well as assigning the response to a local variable inside the function and return that one, but none of those ways actually return the response.

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.

The problem

The A in Ajax stands for asynchronous. That means sending the request (or rather receiving the response) is taken out of the normal execution flow. In your example, $.ajax returns immediately and the next statement, return result;, is executed before the function you passed as success callback was even called.

Here is an analogy which hopefully makes the difference between synchronous and asynchronous flow clearer:

Synchronous

Imagine you make a phone call to a friend and ask him to look something up for you. Although it might take a while, you wait on the phone and stare into space, until your friend gives you the answer you needed.

The same is happening when you make a function call containing "normal" code:

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

var item = findItem();

// Do something with item
doSomethingElse();

Even though findItem might take a long time to execute, any code coming after var item = findItem(); has to wait until the function returns the result.

Asynchronous

You call your friend again for the same reason. But this time you tell him that you are in a hurry and he should call you back on your mobile phone. You hang up, leave the house and do whatever you planned to do. Once your friend calls you back, you are dealing with the information he gave to you.

That's exactly what's happening when you do an Ajax request.

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

Instead of waiting for the response, the execution continues immediately and the statement after the Ajax call is executed. To get the response eventually, you provide a function to be called once the response was received, a callback (notice something? call back ?). Any statement coming after that call is executed before the callback is called.



Solution(s)

Embrace the asynchronous nature of JavaScript! While certain asynchronous operations provide synchronous counterparts (so does "Ajax"), it's generally discouraged to use them, especially in a browser context.

Why is it bad do you ask?

JavaScript runs in the UI thread of the browser and any long running process will lock the UI, making it unresponsive. Additionally, there is an upper limit on the execution time for JavaScript and the browser will ask the user whether to continue the execution or not.

All of this is really bad user experience. The user won't be able to tell whether everything is working fine or not. Furthermore the effect will be worse for users with a slow connection.

In the following we will look at three different solutions that are all building on top of each other:

  • Promises with async/await (ES2017+, available in older browsers if you use a transpiler or regenerator)
  • Callbacks (popular in node)
  • Promises with then() (ES2015+, available in older browsers if you use one of the many promise libraries)

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


ES2017+: Promises with async/await

The new ECMAScript version released in 2017 introduced syntax level support for asynchronous functions. With the help of async and await, you can write asynchronous in a "synchronous style". Make no mistake though: The code is still asynchronous, but it's easier to read/understand.

async/await builds on top of promises: an async function always returns a promise. await "unwraps" a promise and either results in the value the promise was resolved with or throws an error if the promise was rejected.

Important: You can only use await inside an async function. That means that at the very top level, you still have to work directly with the promise.

You can read more about async and await on MDN.

Here is an example that builds on top of delay above:

// 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 a second (just for the sake of this example)
    await delay(1000);
    // 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);
  });

Newer browser and node versions support async/await. You can also support older environments by transforming your code to ES5 with the help of regenerator (or tools that use regenerator, such as Babel).


Let functions accept callbacks

A callback is simply a function passed to another function. That other function can call the function passed whenever it is ready. In the context of asynchronous process, the callback will be called whenever the asynchronous process is done. Usually the result is passed to the callback.

In the example in the question, you can make foo accept a callback and use it as success callback. So this

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

becomes

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

Here we defined the function "inline" but you can pass any function reference:

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

foo(myCallback);

foo itself is defined as follows:

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

callback will refer to the function we pass to foo when we call it and we simply pass it on to success. I.e. once the Ajax request is successful, $.ajax will call callback and pass the response to the callback (which can be referred to with result, since this is how we defined the callback).

You can also process the response before passing it to the callback:

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

It's easier to write code using callbacks than it may seem. After all, JavaScript in the browser is heavily event driven (DOM events). Receiving the Ajax response is nothing else but an event.
Difficulties could arise when you have to work with third party code, but most problems can be solved by just thinking through the application flow.


ES2015+: Promises with then()

The Promise API is a new feature of ECMAScript 6 (ES2015), but it has good browser support already. There are also many libraries which implement the standard Promises API and provide additional methods to ease the use and composition of asynchronous functions (e.g. bluebird).

Promises are containers for future values. When the promise receives the value (it is resolved) or when it is cancelled (rejected), it notifies all of its "listeners" who want to access this value.

The advantage over plain callbacks is that they allow you do decouple your code and they are easier to compose.

Here is a simple example of using 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);
  });
}

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

Applied to our Ajax call we could use promises like this:

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

Describing all the advantages that promises offer is beyond the scope of this answer, but if you write new code, you should seriously consider them. They provide a great abstraction and separation of your code.

More information about promises: HTML5 rocks - JavaScript Promises

Side note: jQuery's deferred objects

Deferred objects are jQuery's custom implementation of promises (before the Promise API was standardized). They behave almost like promises, but expose a slightly different API.

Every Ajax method of jQuery already returns a "deferred object" (actually a promise of a deferred object) which you can just return from your function:

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

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

Side note: Promise gotchas

Keep in mind that promises and deferred objects are just containers for a future value, they are not the value itself. For example, suppose you had the following:

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
}

This code misunderstands the above asynchrony issues. Specifically, $.ajax() doesn't freeze the code while it checks the '/password' page on your server - it sends a request to the server and while it waits, immediately returns a jQuery Ajax Deferred object, not the response from the server. That means the if statement is going to always get this Deferred object, treat it as true, and proceed as though the user is logged in. Not good.

But the fix is easy:

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



Not recommended: Synchronous "Ajax" calls

As I mentioned, some(!) asynchronous operations have synchronous counterparts. I don't advocate their use, but for completeness' sake, here is how you would perform a synchronous call:

Without jQuery

If you directly use a XMLHTTPRequest object, pass false as third argument to .open.

jQuery

If you use jQuery, you can set the async option to false. Note that this option is deprecated since jQuery 1.8. You can then either still use a success callback or access the responseText property of the jqXHR object:

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

If you use any other jQuery Ajax method, such as $.get, $.getJSON, etc., you have to change it to $.ajax (since you can only pass configuration parameters to $.ajax).

Heads up! It is not possible to make a synchronous JSONP request. JSONP by its very nature is always asynchronous (one more reason to not even consider this option).

这篇关于如何从异步调用返回响应?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆