最近想起之前在V2EX上看到的一个问题:一个 async function 数组, 怎样一个一个顺序执行?[1]
想做到的是,每过一秒,分别打印:
this is 0
this is 1
this is 2
~this is 8
下面的代码结果是过一秒后全部执行。
是不是哪里写的不对呢,多谢指教
var jobs = []; for(let i = 0; i < 8; i++) { jobs.push(async function(){ setTimeout(function(){ console.log("this is " + i) },1000) }); } (async function () { for (const job of jobs) { await job() } })();
这题考察对 Promise 、async/await 的理解。而这题又能让人联想到一个类似的setTimeout 循环问题——破解前端面试(80% 应聘者不及格系列):从 闭包说起[2]
请问以下代码打印出什么数据
for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(new Date, i); }, 1000); } console.log(new Date, i);
如何修改成每隔1秒打印一个数,以0、1、2、3、4、5 的顺序排列,并要求原有的代码块中的循环和两处 console.log 不变,该怎么改造代码?
此题面试官想考察循环、闭包、闭包的解决(IIFE)、ES6 知识点(let、Promise)、ES7 的Async/await,以及setTimeout 的第三个参数等等知识点,具体看文章能明白一二
这里我们讲第一道题目,如果要实现题主所说的效果,缺少了什么?
已知:let形成块级作用域,意味着每次循环,jobs就 push 一个 async 函数,这些都是同步执行
但是注意,async 中的函数也是同步执行,只有等到 await 时才会进入微任务中,所以当
- i=0时,jobs 塞入一个 setTimeout(function() { console.log("this is 0" )})
- i=1时,jobs 塞入一个 setTimeout(function() { console.log("this is 1" )})
- i=2时,jobs 塞入一个 setTimeout(function() { console.log("this is 2" )})
- i=3时,jobs 塞入一个 setTimeout(function() { console.log("this is 3" )})
- ...
- i=7时,jobs 塞入一个 setTimeout(function() { console.log("this is 7" )})
继续往下执行
(async function () { for (const job of jobs) { await job() } })();
这里题主了解到 await 需要 async 配合使用,就写了立即执行匿名函数,执行数组 jobs,但问题是 jobs 中的每个子项都是执行 async function(){setTimeout}
,这里的 async 有意义吗?
jobs.push(async function(){ setTimeout(function(){ console.log("this is " + i) },1000) });
如果要让 await 暂停进程并恢复进程(即await job()
),我们需要的是什么?
去掉 async,使其变成一个普通的函数,结果执行结果一致
jobs.push(function () { setTimeout(function () { console.log("this is " + i) }, 1000) });
同样,将普通函数改成箭头函数也是如此,一秒之后打印还是0~7
根据网友[3]总结:
- 对于 promise 对象,await 会阻塞函数执行,等待 promise 的 resolve 返回值,作为 await 的结果,然后再执行下一个表达式
- 对于非 promise 对象,比如 箭头函数、同步表达式等等,await 等待函数或者直接量的返回,而不是等待其执行结果
所以如果要让 await 每隔1秒执行一个 job,那就需返回一个 promise 实例,基于此逻辑进行改造
... jobs.push(function () { return new Promise((resolve, reject) => { setTimeout(function () { resolve() console.log("this is " + i) }, 1000) }) }); ...
这样,就解决了这个问题
我们的逻辑是在循环中,每次向 jobs 中塞入一个函数,这个函数返回的是一个实例 Promise(即 await 遇到后会暂停等异步结束再继续后续代码)。当执行 await job()
时,我们知道是循环 jobs,await 让其等待执行,执行完第一个后,再执行第二个,循序执行,每一个等待1秒钟,就达到题目的要求
这里我们了解到 await 等待的是一个 promise 实例(如果非 promise 实例,就不用等待),既然说到 Promise,我们就延伸一下,then 的链式调用
Promise 的 then 方法支持链式调用,它有哪几种情况?
- 不 return(返回)值, 值延续上一个 resolved
- return
- return 非 Promise 实例
- return Promise 实例
不 return
const promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('johan') }, 2000) }) promise.then((data) => { console.log(data) // 不返回任何值 }).then((data) => { console.log('第二次', data) })
答案:johan、第二次 undefined
return 非 Promise 实例
const promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('johan') }, 2000) }) promise.then((data) => { console.log(data) return '帝王johan' }).then((data) => { console.log('第二次', data) })
答案:johan、第二次 帝王johan
return Promise 实例
const promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('johan') }, 2000) }) promise.then((data) => { console.log(data) return new Promise((resolve, reject) => { setTimeout(() => { resolve(`${data} next`) }, 4000) }) }).then((data) => { console.log('第二次', data) })
答案:johan、第二次 johan next
以上三个例子可以得知,在 then 方法中的 onfulfilled 函数和 onrejected 函数,不仅支持不返回,而且支持非 Promise 实例的普通值,而且支持一个 Promise 实例。并且返回的这个 Promise 实例或非 Promise 实例的普通值将会传给下一个 then 方法的 onfulfilled 函数或者 onrejected 函数中
因为我们知道它是 Generator 函数的语法糖[4],async 函数返回的是一个 Promise 对象,当函数执行时,一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句
我们来一道题来测试一下
const myPromise = val => Promise.resolve(val); const delay = duration => { /**/ }; myPromise(`hello`).then(delay(1000)).then(val => console.log(val)); // hello
myPromise 是个 Promise 实例,传入值 hello,经过 Promise.resolve 传到then 中,然后经 delay 再传递给下一个 then,打印出val,所以 delay(1000) 会返回一个 Promise 实例,这样,第二个 then 才能打印出 hello
const delay = duration => (val) => new Promise((resolve, reject) => { setTimeout(() => { resolve(val) }, duration) })
参考资料
[1]
一个 async function 数组, 怎样一个一个顺序执行?: https://www.v2ex.com/t/810025
[2]
破解前端面试(80% 应聘者不及格系列):从 闭包说起: https://zhuanlan.zhihu.com/p/25855075
[3]
网友: https://blog.csdn.net/qq_38990451/article/details/114869256
[4]
Generator 函数的语法糖: https://es6.ruanyifeng.com/?search=async&x=0&y=0#docs/async