Promise、async/await 与 setInterval

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

执行的结果是:

1
0

这样,肯定是不行的……

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

// 10

为啥 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() 开始执行了");
});

// 0x03 错误示例
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 传入一个函数作为参数,使用:

1
function(){}

而不能用:

1
() => {};

这是因为 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);