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