1. 先简单了解一下什么是async/await
在深入讲解JS循环中使用await会产生什么“化学反应”之前,我们先来梳理一下async/await的相关知识点。
async/await是ES7(ES2017)中引入的新特性,它是Promise的语法糖,使得异步代码看起来更像同步代码。
async函数是返回一个Promise对象的函数,它会自动将普通函数转为Promise对象,然后在函数体内部使用await来等待Promise对象的处理结果。
比如下面的awaitson函数,返回Promise对象
async function awaitson() {
return 1;
}
console.log(awaitson()); // Promise {: 1}
接下来,我们看一下async/await在使用上的注意事项:
async函数必须包含await语句。
await必须在async函数中使用。
await后面可以跟Promise对象或者普通值,如果是普通值则自动包装为一个Promise对象。
如果Promise对象没有被resolve,则async函数会停止执行。
如果Promise对象被reject了,async函数会抛出异常。
那么async/await是如何实现异步操作的呢?其实,它是将异步调用转换为类似同步代码的写法,例如执行await语句时,会阻塞代码执行,直到Promise对象被resolve或reject。
2. 循环中使用await会发生什么
2.1 使用await in for循环
在for循环中使用async/await,我们需要注意两个细节:
Promise对象在await之前需要先执行完成才会执行下一次循环。
由于循环异步进行,遇到await后会暂停执行,所以循环中的异步操作并非按序执行,可能会导致一些bug。
详细代码说明如下:
async function allFunc() {
const arr = [1, 2, 3, 4, 5];
for (let i = 0; i < arr.length; i++) {
const result = await fetch(`https://jsonplaceholder.typicode.com/todos/${arr[i]}`);
console.log(`异步获取数据${arr[i]}: `, await result.json()); // 数据调用
}
console.log('操作完成');
}
allFunc();
程序执行结果如下:
异步获取数据1: {userId: 1, id: 1, title: "delectus aut autem", completed: false}
异步获取数据2: {userId: 1, id: 2, title: "quis ut nam facilis et officia qui", completed: false}
异步获取数据3: {userId: 1, id: 3, title: "fugiat veniam minus", completed: false}
异步获取数据4: {userId: 1, id: 4, title: "et porro tempora", completed: true}
异步获取数据5: {userId: 1, id: 5, title: "laboriosam mollitia et enim quasi adipisci quia provident illum", completed: false}
操作完成
从执行结果来看,Promise会按顺序执行,并且所有异步操作完成后,才会执行后续代码。
2.2 使用await in forEach循环
forEach循环中,我们不能使用await来暂停代码执行,因为它不会等待异步操作完成就会进行下一次遍历。
对forEach和普通for循环的比较,详见下方代码:
//使用forEach循环获取数据
async function forEachFunc () {
const arr = [1, 2, 3, 4, 5];
arr.forEach(async (item) => {
const result = await fetch(`https://jsonplaceholder.typicode.com/todos/${item}`);
console.log('异步获取的结果', await result.json()); // undefined
});
console.log('操作完成');
}
forEachFunc();
//使用普通for循环获取数据
async function forFunc() {
const arr = [1, 2, 3, 4, 5];
for(let i = 0; i < arr.length; i++) {
const result = await fetch(`https://jsonplaceholder.typicode.com/todos/${arr[i]}`);
console.log('异步获取的结果', await result.json()); // 数据调用
if(i === arr.length - 1) {
console.log('操作完成');
}
}
}
forFunc();
代码执行结果如下:
异步获取的结果 undefined
异步获取的结果 { userId: 1, id: 1, title: 'delectus aut autem', completed: false }
异步获取的结果 { userId: 1, id: 2, title: 'quis ut nam facilis et officia qui', completed: false }
异步获取的结果 { userId: 1, id: 3, title: 'fugiat veniam minus', completed: false }
异步获取的结果 { userId: 1, id: 4, title: 'et porro tempora', completed: true }
异步获取的结果 { userId: 1, id: 5, title: 'laboriosam mollitia et enim quasi adipisci quia provident illum', completed: false }
操作完成
异步获取的结果 { userId: 1, id: 1, title: 'delectus aut autem', completed: false }
异步获取的结果 { userId: 1, id: 2, title: 'quis ut nam facilis et officia qui', completed: false }
异步获取的结果 { userId: 1, id: 3, title: 'fugiat veniam minus', completed: false }
异步获取的结果 { userId: 1, id: 4, title: 'et porro tempora', completed: true }
异步获取的结果 { userId: 1, id: 5, title: 'laboriosam mollitia et enim quasi adipisci quia provident illum', completed: false }
操作完成
从结果可以看出,使用forEach循环获取异步数据时,会执行所有的异步操作,最后才会输出“操作完成”,说明循环遍历是异步进行的,没有等待异步操作完成。
而在使用for循环获取数据时,所有的console.log()都等到了异步获取数据后才会输出,说明循环遍历是同步进行的。
2.3 await出现在函数中return之后
await出现在函数中return之后,会直接返回一个Promise对象。
async function asyncFunc () {
await 'async';
return 'return';
}
console.log(asyncFunc());
执行结果如下:Promise {
为何会这样?因为async/await语法糖本身就是Promise的语法糖,async就是返回一个Promise对象,而在函数中使用await是等待Promise,所以这个Promise对象一直是pending状态。
3. 建议和总结
3.1 建议
对于单个请求,我们可以使用async/await方法来实现。
对于多个请求,使用Promise.all([])可以实现并行异步操作。
当我们处理多个异步操作,而这些操作需要先后顺序时,这时就需要使用Promise来实现异步操作。
3.2 总结
在ES7之前,我们常使用回调函数或Promise进行异步操作处理。但是这种方式还是太难维护、扩展,迭代成本高等问题限制了异步操作的发展。而ES7新引入的async/await,使代码简洁、易懂。同时,async/await等语法糖也能够使代码执行时更直观地表达出异步与同步的关系。但在使用它时,还是有一些需要注意的地方,比如循环中使用await等问题。因此,在使用async/await进行异步操作时,开发者应该对其进行深入了解,并在实际项目中进行规范的使用。
目前,JS异步编程掌握async/await应该足以提升我们的实际编码能力。接下来,我们可以结合Promise等其他技术来实践更复杂的业务场景。这样可以更好地掌握JS异步编程,进一步提升自我在JS开发中的技术水平和团队开发的效率。