彻底搞清 JavaScript Promise

基本用法

我们知道,一个 js Promise 对象,我们可以用 then()catch()finally() 进行链式操作,如:

1
2
3
4
5
6
7
promiseObj.then((res) => {
// do something
}).catch((error) => {
// do something
}).finally(() => {
// do something
})

这是 js Promise 的基本使用,但是要用好 Promise,还有一下几个问题需要搞清楚。

怎样创建 Promise 对象

Promise 对象创建 Promise 实例

1
2
3
4
5
6
7
8
9
// 创建 Promise 对象
const promiseObj = new Promise((resolve, reject) => {
// 这里执行你的业务逻辑
if (fail) {
return reject('执行失败参数');
}

return resolve('执行成功参数');
});

async 创建 Promise 实例

async 函数返回一个 Promise 实例,我们可以用来创建 Promise 实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
async function prm () {
// throw new Error('出错了1'); // 如果不正常,抛出错误,触发 catch() 方法,并把此错误实例传参给 catch()
return 100; // 正常,返回值传参给第一个 then() 方法
}

prm().then(res => {
// do something
}).catch(err => {
// do something
});

// 用匿名函数立即执行函数体创建实例
const x = (async () => {
// throw new Error('出错了1'); // 如果不正常,抛出错误,触发 catch() 方法,并把此错误实例传参给 catch()
return 100; // 正常,返回值传参给第一个 then() 方法
})();

x.then(res => {
// do something
}).catch(err => {
// do something
});

链式调用时参数是怎样传递的

then() 参数的传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建 Promise 对象
const promiseObj = new Promise(resolve => {
return resolve(0);
});
promiseObj.then((res) => {
console.log(1, res); // 1, 0
return 1;
}).then((res) => {
console.log(2, res); // 2, 1
return 2;
}).then((res) => {
console.log(3, res); // 3, 2
return 3;
})

执行上面的代码,我们发现,第1个 then() 的参数是 resolve() 的参数值,第2个 then() 及之后的 then() 的参数值都是其上一个 then() 的返回值。

catch() 参数的传递

catch() 参数的传递和 then() 不同,要把错误从 catch() 传给下一个 catch(),需要在 catch() 中抛出错误。

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
37
Promise.resolve().then(() => {
console.log(1);
throw new Error('err');
}).catch((err) => {
console.log(2);
// throw err;
return 'err...'; // catch 中没有抛出错误,返回值将传给下一个 then()
}).catch(() => {
console.log(3);
}).then(res => {
console.log(4);
console.log(res);
});
// 结果:
// 1
// 2
// 4
// err...

Promise.resolve().then(() => {
console.log(1);
throw new Error('err');
}).catch((err) => {
console.log(2);
throw err;
// return 'err...';
}).catch(() => {
console.log(3);
}).then(res => {
console.log(4);
console.log(res);
});
// 结果:
// 1
// 2
// 3
// 4

在 then() 中怎样传值给上级代码块

js Promise 是异步的,同一个 Promisethen() 方法是按代码顺序的一个 then 到下一个 than 的执行,但和than 外部的代码块是异步的。如果要把 then 代码块的值传给上层的代码块,回调是常用的方法,但如果上层代码块还需要返回异代码块中的值的时候,回调就做不到了。

ES2017 标准引入了 async 函数,使得异步操作变得更加方便。

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先等待,等到该处的异步操作完成,再接着执行函数体内后面的语句。加上 await 后,将返回回调函数的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const prm = new Promise((resolve, reject) => {
resolve();
});

let a = 11;
prm.then(res => { // 这里是异步的,后面的代码和这里分两路同时执行
a = 22;
});
console.log(a); // 11

async function test () {
let a = 11;
// 这里加了 await,告诉后面的代码说先等等,我这个代码块执行完了你再继续。
a = await prm.then(res => {
// 返回值传到 a
return 22;
});
console.log(a); // 22
}
test();

注意

1、await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}

// 另一种写法
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err);
});
}

2、多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

1
2
let foo = await getFoo();
let bar = await getBar();

上面代码中,getFoogetBar 是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有 getFoo 完成以后,才会执行 getBar,完全可以让它们同时触发。

1
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

3、await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。从 ES2022 开始允许 await 可以在顶层使用,不需要在 async 函数中。

参考: ECMAScript 6 入门

作者

CoderPan

发布于

2023-02-09

更新于

2024-07-18

许可协议

评论