0%

promise

Introduction

Promise就像它的名字一样,是指在未来某个时间将会发生的事情。这个事情会生成一个结果,我们叫做Result。

Promise有三种状态:

  • Pending: 初始状态,既不是成功状态,也不是失败状态。
  • Fulfilled: 意味着操作成功完成。
  • Rejected: 意味着操作失败。

这里,FullfilledRejected又统称为Settled。也就是说,一个Promise只要执行完毕有就算是Settled了, 无论是成功还是失败。

Promise API

Promise.all

Promise.all接收一个promise数组,返回一个新的promise。这个新的promise会在所有promise都resolve之后resolve,或者在任何一个promise reject之后reject。

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

const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise 2 resolved');
}, 2000);
});

Promise.all([promise1, promise2]).then((values) => {
console.log(values);
});
``
output:
```javascript
['Promise 1 resolved', 'Promise 2 resolved']

假设Promise 2 reject了,那么Promise.all会reject,并且返回的promise的值是Promise 2 reject的值。

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

const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(Error('Promise 2 rejected'));
}, 2000);
});

Promise.all([promise1, promise2])
.then((values) => {
console.log(values);
})
.catch((error) => {
console.error(error);
});

output:

1
Error: Promise 2 rejected

注意,Promise.all大部分情况下是异步返回的,只有一种情况例外,那就是传递一个空数组。

1
Promise.all([]); // 立即同步返回 Promise { [] }

我们可以使用console.log来验证上述代码是同步返回的

1
2
3
4
console.log('start');
const p = Promise.all([]);
console.log(p);
console.log('end');

输出结果如下,可见Promise.all([])是同步返回的。

1
2
3
start
Promise { [] }
end

除了传递空数组外,其他所有情况都是异步返回,比如直接传递非promise数组。

1
const p = Promise.all([1, 2, 3]); // 异步返回。

思考题:
以下代码输会输出promise one running...吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('promise one running...');
resolve('one');
}, 1000);
});

const p2 = new Promise((resolve, reject) => {
reject('rejected');
});

Promise.all([p1, p2])
.then((values) => {
console.log(values);
})
.catch((error) => {
console.error(error);
});

解析:会输出’promise one running…’, 虽然Promise.all会在任何一个promise reject之后立即reject,但是剩下的promise还是会继续执行,直到resolve或者reject。注意这就是promise的不可取消性,如何要取消,请使用RxJS中的observable。

到这里我们需要总结一下Promise.all的返回值,一共有以下几种情况:

  1. 所有promise都resolve,返回一个数组,数组中包含了所有promise的resolve值。
  2. 任何一个promise reject,返回的promise会reject,其值是第一个reject的promise的值。
  3. 传递一个空数组,返回的promise会立即resolve,值是一个空数组。

Promise.allSettled

这个方法与Promise.all类似,不同的是,Promise.allSettled会等待所有promise都settled之后返回。settled的意思是promise已经resolve或者reject了。

Promise.allSettled的返回值是一个数组,数组中的每个元素都是一个对象,包含了promise的状态和值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('one');
}, 1000);
});

const p2 = new Promise((resolve, reject) => {
reject('rejected');
});

// Using .catch:
Promise.allSettled([p1, p2])
.then((values) => {
console.log(values);
})
.catch((error) => {
console.error(error);
});

以上代码输出如下:

1
2
3
4
[
{ status: 'fulfilled', value: 'one' },
{ status: 'rejected', reason: 'rejected' }
]

Promise.any

输入的Promise数组中,任意一个promise resolve,返回的promise就resolve。如果所有的promise都reject,返回的promise就reject。

以下代码输出quick,因为p2最快resolve。

1
2
3
4
5
6
const p1 = Promise.reject(0);
const p2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'));
const p3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'));

const promises = [p1, p2, p3];
Promise.any(promises).then((value) => console.log(value));

当所有promise都reject时,Promise.any会返回``AggregateError`,这个错误包含了所有的reject值。

1
2
3
4
const p1 = Promise.reject(0);
const p2 = Promise.reject(1);
const promises = [p1, p2];
Promise.any(promises).then((value) => console.log(value));

如果给Promise.any传递一个空数组,返回的promise会reject,错误信息是[AggregateError: All promises were rejected] { [errors]: [] }.

Promise.race

这个API最简单了,看名字就知道了,谁先返回就是谁,不论是resolve还是reject。
Promise.race接收一个promise数组,返回一个新的promise。这个新的promise会在任意一个promise resolve或者reject之后resolve或者reject。

下面代码输出two,因为p2最快resolve。

1
2
3
4
5
6
7
8
9
10
11
const p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});

const p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});

Promise.race([p1, p2]).then((value) => {
console.log(value);
});

注意:如果给Promise.race传递一个空数组,返回的promise会一直pending,因为没有promise可以resolve或者reject。这就好比一个没有人参加的百米赛跑,永远不会有人冲过终点。

Promise in setTimeout

setTimeout is macro-task, and promise is micro-task. So the promise will be executed before the setTimeout.

1
2
3
4
5
6
7
8
9
10
const promise1 = Promise.resolve('Promise 1 resolved');
const promise2 = Promise.resolve('Promise 2 resolved');

const promise = Promise.all([promise1, promise2]);
console.log(promise);

// Using setTimeout, we can execute code after the queue is empty
setTimeout(() => {
console.log(promise);
});

output:

1
2
Promise { <pending> }
Promise { [ 'Promise 1 resolved', 'Promise 2 resolved' ] }

Why the output?

  1. Promise.all is a micro-task, so it will be executed before the setTimeout macro-task.
  2. The promise is pending when we log it for the first time.
  3. After the Promise.all is executed, the promise is resolved
  4. The setTimeout is a macro-task, so it will be executed after the queue is empty(no micro-task in the queue).
  5. The promise is resolved when we log it for the second time