TL;DR;
易混淆点:async 修饰的函数,不管函数内部 return 的是啥,最后都返回一个 Promise。而 return 返回的值,最后在 Promise.then() 里取到。
async 修饰一个函数,只是说明会隐式地返回一个 Promise,并不是说 这个函数内的操作都在一个 Promise 内。
也就是说,async 修饰一个方法时,并不能保证方法里的操作同步执行。 (一般 async 修饰的方法,同步操作会使用关键字 await 。)
需求
定时循环执行一个数学计算,把计算出来的值返回并打印出来。
分析
不假思索写出一段代码:
1 2 3 4 5 6 7 8 9
| let count = 0; setInterval(function () { count++; if (count === 3) { clearInterval(this); } }, 1000);
console.log(count);
|
执行的结果是:
这样,肯定是不行的……
JavaScript 是异步的,实际上这段代码里的 setInterval() 和 console.log() 会同时执行。
要使它们的执行有一个先后顺序,这就涉及到 JavaScript 里如何「同步」的问题了。
同步的方式有许多种,这里谈谈 Promise 和 async/await。
Promise 和 setInterval
0x01 错误示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function testA() { console.log("testA() 开始执行……"); return new Promise(function (resolve, reject) { let count = 0; setInterval(function () { count++; console.log("testA() setTimeout 内容……"); if (count === 3) { clearInterval(this); } }, 2000); resolve("testA() 返回内容,计算得到 count = " + count); }); }
testA().then((data) => { console.log(data); console.log("testA.then() 开始执行"); });
|
执行的结果是:
1 2 3 4 5 6
| testA() 开始执行…… testA() 返回内容,计算得到 count = 0 testA.then() 开始执行 testA() setTimeout 内容…… testA() setTimeout 内容…… testA() setTimeout 内容……
|
0x01 分析
Promise 的构造函数接受一个函数作为参数,这个函数又使用两个函数作为参数:resolve、reject。
简单理解的话,resolve 和 reject 这两个函数都用于在 Promise 里返回值。后面调用 Promise 的 then() 时,就可以获取到返回的值。他们的不同之处在于 resolve 将 Promise 的状态标志为「成功」,而 reject 将 Promise 的状态标志为「失败」(通常对应遇到异常)。
这段代码的问题和上面的一样,即 setInterval 与 resolve 会同时运行。所以执行 testA 后,早早地拿到了上面根本没有进入 setInterval 进行运算的 count 的值。
0x02 这样有效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function testA() { console.log("testA() 开始执行……"); return new Promise(function (resolve, reject) { let count = 0; setInterval(function () { count++; console.log("testA() setTimeout 内容……"); if (count === 3) { clearInterval(this); resolve("testA() 返回内容,计算得到 count = " + count); } }, 2000); }); }
testA().then((data) => { console.log(data); console.log("testA.then() 开始执行"); });
|
执行的结果是:
1 2 3 4 5 6
| testA() 开始执行…… testA() setTimeout 内容…… testA() setTimeout 内容…… testA() setTimeout 内容…… testA() 返回内容,计算得到 count = 3 testA.then() 开始执行
|
0x02 分析
我们把 resolve 也放进 setInterval 里,当计算完成后再返回,这样,就能保证拿到计算后的值啦。
async await 与 setInterval
我想整高级点,用 async 与 await 来重写,应该咋整?
0x03 错误示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| async function testA() { console.log("testA() 开始执行……"); let count = 0; setInterval(function () { count++; console.log("testA() setTimeout 内容……"); if (count === 3) { clearInterval(this); } }, 1000); return "testA() 返回内容,计算得到 count = " + count; }
testA().then((data) => { console.log(data); console.log("testA.then() 开始执行了"); });
|
执行的结果是:
1 2 3 4 5 6
| testA() 开始执行…… testA() 返回内容,计算得到 count = 0 testA.then() 开始执行了 testA() setTimeout 内容…… testA() setTimeout 内容…… testA() setTimeout 内容……
|
0x03 分析
同理,setInterval 与 return 会同时运行。所以执行 testA 后,早早地拿到了上面根本没有进入 setInterval 进行运算的值。
0x04 还是错误的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| async function testA() { console.log("testA() 开始执行……"); let count = 0; setInterval(function () { count++; console.log("testA() setTimeout 内容……"); if (count === 3) { clearInterval(this); return "testA() 返回内容,计算得到 count = " + count; } }, 1000); }
testA().then((data) => { console.log(data); console.log("testA.then() 开始执行了"); });
|
执行的结果是:
1 2 3 4 5 6
| testA() 开始执行…… undefined testA.then() 开始执行了 testA() setTimeout 内容…… testA() setTimeout 内容…… testA() setTimeout 内容……
|
0x04 分析
想当然地用类似上面 Promise 的方法,把 return 放在 setInterval 里面,还是不行。
获取到的 data 是 undefined。这说明有个隐式的 return 和 setInterval 一起执行啦……
这可咋整啊……
setInterval 是异步的,咋整,用最上面的方法,套个 Promise 呗……可这不就又回到最初的起点了吗……
0x05 这样可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| async function testA() { console.log("testA() 开始执行……"); let result = await new Promise((resolve, reject) => { let count = 0; setInterval(function () { count++; console.log("testA() setTimeout 内容……"); if (count === 3) { clearInterval(this); resolve("testA() 返回内容,计算得到 count = " + count); } }, 2000); }); console.log("testA 延迟后的内容"); return result; }
testA().then((result) => { console.log(result); console.log("testA.then() 开始执行了"); });
|
结果:
1 2 3 4 5 6 7
| testA() 开始执行…… testA() setTimeout 内容…… testA() setTimeout 内容…… testA() setTimeout 内容…… testA 延迟后的内容 testA() 返回内容,计算得到 count = 3 testA.then() 开始执行了
|
0x05 分析
把 setInterval 包装到一个 Promise 里并返回,然后使用 await 等待这个 Promise(包装着 setInterval)执行完并取得返回值。
再把这个返回值返回。
为啥呢
async 修饰的函数,不管 return 的是啥,最后都返回一个 Promise。而 return 返回的值,最后在 Promise.then() 里取到。
例如:
1 2 3 4 5 6 7 8 9
| async function getCount() { return 10; }
getCount().then((count) => { console.log(count); });
|
为啥 0x03 和 0x04 是错误的呢?
因为 async 修饰一个函数,只是说明会隐式地返回一个 Promise,并不是说 这个函数内的操作都在一个 Promise 内。
也就是说,async 修饰一个方法,并不能保证方法里的操作同步执行。
之前我把 async 这类关键字与 Java 里面的一些关键字对比理解……
按我的理解,「0x03 错误示例」就相当于:
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 35 36
| function testA() { console.log("testA() 开始执行……"); let count = 0; setInterval(function () { count++; console.log("testA() setTimeout 内容……"); if (count === 3) { clearInterval(this); } }, 1000); return Promise.resolve("testA() 返回内容,计算得到 count = " + count); }
testA().then((data) => { console.log(data); console.log("testA.then() 开始执行了"); });
async function testA() { console.log("testA() 开始执行……"); let count = 0; setInterval(function () { count++; console.log("testA() setTimeout 内容……"); if (count === 3) { clearInterval(this); } }, 1000); return "testA() 返回内容,计算得到 count = " + count; }
testA().then((data) => { console.log(data); console.log("testA.then() 开始执行了"); });
|
其它
虽然标题是:「Promise、async/await 与 setInterval」,实际上 setTimeout 也是一个道理。
箭头函数
我在这篇文章里用 setInterval 时,用的是:
1 2 3 4 5 6 7 8
| let count = 0; setInterval(function () { count++; console.log("testA() setTimeout 内容……"); if (count === 3) { clearInterval(this); } }, 1000);
|
setInterval 传入一个函数作为参数,使用:
而不能用:
这是因为 function(){} 函数里的 this 指的是 function(){} 本身,而 ()=>{} 里的 this 则指的是 ()=>{} 外部的环境。
按定义来讲则是:箭头函数没有 this。如果访问 this,则从外部获取。
如果我们在这用 ()=>{} 的话,如下:
1 2 3 4 5 6 7 8
| let count = 0; setInterval(() => { count++; console.log("testA() setTimeout 内容……"); if (count === 3) { clearInterval(this); } }, 1000);
|
这里的 setInterval 则会一直循环执行,不会停止。
因为 clearInterval(this) 根本没得效果。
如果要用箭头函数的话,需要这样:
1 2 3 4 5 6 7 8
| let count = 0; let interval = setInterval(() => { count++; console.log("testA() setTimeout 内容……"); if (count === 3) { clearInterval(interval); } }, 1000);
|