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

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

技术背景

在JavaScript编程中,异步操作是常见的需求,如AJAX请求、定时器等。AJAX中的“A”代表“异步(Asynchronous)”,这意味着发送请求(或接收响应)会从正常的执行流程中分离出来。在异步调用中,请求发送后,代码不会等待响应返回就会继续执行后续语句,这就导致直接从异步调用中返回响应值会出现问题,返回的往往是undefined

实现步骤

1. 理解异步和同步的区别

  • 同步:代码按顺序依次执行,一个操作完成后才会执行下一个操作。例如,函数调用会等待函数执行完毕并返回结果后,才会继续执行后续代码。
1
2
3
4
5
6
7
8
9
10
11
function findItem() {
var item;
while(item_not_found) {
// search
}
return item;
}

var item = findItem();
// Do something with item
doSomethingElse();
  • 异步:操作不会阻塞后续代码的执行,而是在操作完成后通过回调函数通知。例如,AJAX请求发送后,代码会继续执行后续语句,当请求响应返回时,会调用预先定义的回调函数处理响应。
1
2
3
4
findItem(function(item) {
// Do something with the item
});
doSomethingElse();

2. 解决方案

2.1 ES2017+:使用async/await结合Promise

async/await是ES2017引入的语法糖,用于以同步的方式编写异步代码。async函数总是返回一个Promiseawait关键字用于等待Promise的解决或拒绝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 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 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;
}
}

// Start an IIFE to use `await` at the top level
(async function(){
let books = await getAllBooks();
console.log(books);
})();

2.2 让函数接受回调函数

将处理响应的逻辑封装在回调函数中,当异步操作完成时,调用该回调函数并传递响应结果。

1
2
3
4
5
6
7
8
9
10
11
12
function foo(callback) {
$.ajax({
// ...
success: callback
});
}

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

foo(myCallback);

2.3 ES2015+:使用Promisethen()方法

Promise是ES2015引入的一种处理异步操作的方式,它有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。可以通过then()方法处理Promise解决后的结果,通过catch()方法处理Promise拒绝的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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).
});

3. 处理数组的异步操作

3.1 并行处理

可以同时启动所有异步操作,并记录需要等待的回调数量,当所有回调都完成时,使用结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function doSomethingWith(theArray, callback) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
callback(results);
}
});
});
}

doSomethingWith([1, 2, 3], function(results) {
console.log("Results:", results);
});

3.2 串行处理

如果需要按顺序依次执行异步操作,可以使用递归或async/await结合for-of循环。

1
2
3
4
5
6
7
8
9
10
11
async function doSomethingWith(theArray) {
const results = [];
for (const entry of theArray) {
results.push(await doSomethingAsync(entry));
}
return results;
}

doSomethingWith([1, 2, 3]).then(results => {
console.log("Results:", results);
});

核心代码

使用async/await

1
2
3
4
5
6
7
8
9
10
11
12
13
14
async function getAllBooks() {
try {
var bookIDs = await superagent.get('/user/books');
await delay();
return superagent.get('/books/ids='+JSON.stringify(bookIDs));
} catch(error) {
return null;
}
}

(async function(){
let books = await getAllBooks();
console.log(books);
})();

使用回调函数

1
2
3
4
5
6
7
8
9
10
11
function foo(callback) {
$.ajax({
success: callback
});
}

function myCallback(result) {
console.log(result);
}

foo(myCallback);

使用Promisethen()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function delay() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(42);
}, 3000);
});
}

delay()
.then(function(v) {
console.log(v);
})
.catch(function(v) {
console.log(v);
});

最佳实践

  • 避免使用同步AJAX:同步AJAX会阻塞浏览器的UI线程,导致页面无响应,影响用户体验。
  • 使用Promiseasync/await:这两种方式可以使异步代码更易于阅读和维护,避免回调地狱。
  • 错误处理:在异步操作中,要确保对可能出现的错误进行处理,使用try...catch块或catch()方法。

常见问题

1. 为什么直接返回异步调用的结果是undefined

因为异步操作不会阻塞后续代码的执行,在异步操作完成之前,返回语句就已经执行了,此时结果还未得到,所以返回undefined

2. 如何处理多个异步操作的结果?

可以使用Promise.all()并行处理多个Promise,或者使用async/await结合for-of循环串行处理。

3. 什么情况下使用回调函数,什么情况下使用Promiseasync/await

  • 回调函数:适用于简单的异步操作,代码量较少时。
  • Promiseasync/await:适用于复杂的异步操作,需要处理多个异步操作的顺序和错误时。

如何从异步调用中返回响应
https://119291.xyz/posts/2025-05-07.how-to-return-response-from-asynchronous-call/
作者
ww
发布于
2025年5月7日
许可协议