- 缓解回调地狱
- 让两个异步操作并行,在都结束后提醒你
- 让两个异步操作并行,只关注首个结束结果
生命周期
Promise有三个状态(内部的[[PromiseState]]
属性):
- pending 挂起
- fulfilled 成功
- rejected 失败
then()
方法在所有Promise上都存在并且接受两个参数。第一个参数是Promise被完成时要调用的函数,与异步操作关联的任何附加数据都会被传入这个完成函数。第二个参数则是Promise被拒绝时要调用的函数,与完成函数相似,拒绝函数会被传入与拒绝相关联的 任何附加数据。
用这种方式实现then()
方法的任何对象都被称为一个 thenable 。所有的Promise都是thenable,反之则未必成立。
每次调用then()
或catch()
都会创建一个 新的作业,他会在Promise已决议时执行。但这些作业最终会进入一个完全为Promise保留的作业队列。
创建未决的Promise
1 | let promise = new Promise(function(resolve, reject) { |
创建已决的Promise
使用Promise.resolve()
Promise.resolve()
方法接受单个参数并会返回一个处于完成态的Promise。这意味着没有任何作业调度会发生,并且你需要向Promise添加一个或更多的完成处理函数来提取这个参数值。例如:
1 | let promise = Promise.resolve(42); |
使用Promise.reject()
你也可以使用Promise.reject()
。此方法像Promise.resolve()
一样工作,区别是被创建的 Promise 处于拒绝态,如下:
1 | let promise = Promise.reject(42); |
传入Promise
- 若传入的 Promise 为挂起态
Promise.resolve()调用不会有返回。此后,若决议原 Promise ,在 then() 中可以接收到原例中的参数42,而若拒绝原 Promise,则在 catch() 中可以接收到参数 42。但 Promise.reject()调用则会对原先的 Promise 重新进行包装,对其使用 catch()可以捕捉到错误,处理函数中的value参数不会是数值42,而是原先处于挂起态的Promise1
2
3
4
5
6
7
8
9
10
11
12let promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(42)
}, 1000)
})
let promise1 = Promise.resolve(promise);
promise1.then((res) => {
console.log(res)
}).catch(e => {
console.log('error', e)//error 42
})但1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24let promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(42)
}, 1000)
})
let promise1 = Promise.reject(promise)
promise1.then((res) => {
console.log(res)
}).catch(e => {
console.log('error', e)// error Promise<pending>
})
```
* 若传入的Promise为完成状态
`Promise.resolve()`调用会将该Promise原样返回,在 `then()`中可以接收到原例中的参数42
```js
let promise = Promise.resolve(42)
let promise1 = Promise.resolve(promise)
promise1.then((res) => {
console.log(res) // 42
}).catch(e => {
console.log('error', e)
})Promise.reject()
调用则会对原先的Promise重新进行包装,对其使用catch()
可以捕捉到错误,处理函数中的value参数不会是数值42,而是原先处于完成态的Promise。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17let promise = Promise.resolve(42)
let promise1 = Promise.reject(promise)
promise1.then((res) => {
console.log(res)
}).catch(e => {
console.log('error', e) // error Promise {<resolved>: 42}
})
```
* 若传入的Promise为拒绝态
`Promise.resolve()`调用会将该Promise原样返回,在`catch()`中可以接收到参数42。
```js
let promise1 = Promise.resolve(promise)
promise1.then((res) => {
console.log(res)
}).catch(e => {
console.log('error', e)// error 42
})Promise.reject()
调用则会对原先的Promise重新进行包装,对其使用catch()
可以进行完成处理,处理函数中的value参数不是42,而是原先处于拒绝态的Promise。1
2
3
4
5
6
7
8let promise = Promise.reject(42)
let promise1 = Promise.reject(promise)
promise1.then((res) => {
console.log(res)
}).catch(e => {
console.log('error', e)//error Promise {<rejected>: 42}
})
非Promise的Thenable
Promise.resolve()
与Promise.reject()
都能接受非Promise的thenable作为参数。当传入了非Promise的thenable时,这些方法会创建一个新的Promise,此Promise会在then()
函数之后被调用。
当一个对象拥有一个能接受resolve
与reject
参数的then()
方法,该对象就会被认为是一个非Promise的thenable,就像这样:
1 | let thenable = { |
此例中的
thenable
对象,除了then()
方法之外没有任何与Promise相关的的特征。你可以调用Promise.resolve()
来将thenable
转换为一个已完成的Promise:
1 | let thenable = { |
此例子中,
Promise.resolve()
调用了thenable.then()
,确定了这个thenable
的Promise状态:由于resolve(42)
在thenable.then()
方法内部被调用,这个thenable
的Promise状态也就被设为已完成。一个名为p1
的完成处理函数就接收到一个值为42的参数。
执行器错误
Promise的拒绝处理函数会处理执行器内部抛出的所有错误
全局Promise拒绝处理
如果promise没有catch,抛出来的错误就不会被抛出,node.js和浏览器都提供了相应的事件来监听是否存在没有被监听的错误。
node.js
提供两个事件:
unhandledRejection
:当一个Promise被拒绝、而在事件循环的一个轮次中没有任何拒绝处理函数被调用,改时间就会被触发。rejectionHandled
:若一个Promise被拒绝、并在事件循环的一个轮次之后再有拒绝处理函数被调用,该事件就会被触发。1
2
3
4
5
6
7
8let process = require('process');
let rejected;
process.on("unhandledRejection", function(reason, promise) {
console.log(reason.message);// a
console.log(reason);// 错误堆栈
console.log(rejected === promise);// true
});
rejected = Promise.reject(new Error('a'))事件处理函数接受两个参数,第一个参数为错误对象,第二个为原Promise
rejectionHandled
事件处理函数则只有一个参数,即已被拒绝的Promise。如:1
2
3
4
5
6
7
8
9
10
11let process = require('process');
let rejected;
process.on("rejectionHandled", function(promise) {
console.log('res========>',rejected === promise);
});
rejected = Promise.reject(new Error('a'))
setTimeout(() => {
rejected.catch(e => {
console.log(e)
})
}, 1000)此处的
rejectionHandled
事件在拒绝处理函数最终被调用时触发。若在rejected
被创建后直接将拒绝处理函数附加到它上面,那么此事件就不会被触发。因为立即附加的拒绝处理函数在rejected
被创建的事件循环的同一个轮次内就会被调用,这样rejectionHandled
就不会起作用。为了正确追踪潜在的未被处理的拒绝,使用
rejectionHandled
与unhandleRejection
事件就能保持包含这些Promise的一个列表。如: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
38
39
40
41
42
43
44
45
46
47let possiblyUnhandledRejections = new Map();
// 当一个拒绝未被处理,将其添加到 map
process.on("unhandledRejection", function(reason, promise) {
possiblyUnhandledRejections.set(promise, reason);
});
process.on("rejectionHandled", function(promise) {
possiblyUnhandledRejections.delete(promise);
});
setInterval(function() {
possiblyUnhandledRejections.forEach(function(reason, promise) {
console.log(reason.message ? reason.message : reason);
// 做点事来处理这些拒绝
handleRejection(promise, reason);
});
possiblyUnhandledRejections.clear();
}, 60000);
```
- - - -
##### 浏览器
浏览器同样能触发两个事件来识别未处理的拒绝。这两个事件由`window`触发,完全等效于Node.js的相关事件:
* `unhandledrejection`:当一个Promise被拒绝、而在事件循环的一个轮次中没有任何拒绝处理函数被调用,该事件就会被触发;
* `rejectionHandled`:若一个Promise被拒绝、并在事件循环的一个轮次之后再由拒绝处理函数被调用,该事件就会被触发。
与Node.js不同的是浏览器只会给事件处理函数一个对象,其包含以下属性:
* `type`:事件的名称(`unhandlerejection`或`rejectionhandled`);
* `promise`:被拒绝的Promise对象;
* `reason`:Promise中的拒绝值(拒绝原因)。
**拒绝值在两种事件中都可用**。如:
```js
let rejected;
window.onunhandledrejection = event => {
console.log(event.type);
console.log(event.reason.message);
console.log(rejected === event.promise);
};
window.onrejectionhandled = function(event) {
console.log(event.type);
console.log(event.reason.message);
console.log(rejected === event.promise);
};
rejected = Promise.reject(new Error("Explosion!"));在chrome中受同源策略的影响必须在一个服务上运行
与之前node中相似的代码
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
36let possiblyUnhandledRejections = new Map();
// 当一个拒绝未被处理,将其添加到 map
window.onunhandledrejection = function(event) {
possiblyUnhandledRejections.set(event.promise, event.reason);
};
window.onrejectionhandled = function(event) {
possiblyUnhandledRejections.delete(event.promise);
};
setInterval(function() {
possiblyUnhandledRejections.forEach(function(reason, promise) {
console.log(reason.message ? reason.message : reason);
// 做点事来处理这些拒绝
handleRejection(promise, reason);
});
possiblyUnhandledRejections.clear();
}, 1000);
```
- - - -
### 串联Promise
每次对`then()`或`catch()`的调用实际上创建并返回了另一个Promise,仅当前一个Promise被完成或拒绝时,后一个Promise才会被决议。
```js
let p1 = new Promise(function(resolve, reject) {
resolve(42);
});
p1.then(function(value) {
console.log(value);
}).then(function() {
console.log("Finished");
});
// 输出为:
// 42
// Finished
捕获错误
为了确保能正确处理任意可能发生的错误,应当始终在Promise链尾部添加拒绝处理函数。
在Promise链中返回Promise
在许多情况下我们需要多个异步操作来完成一些任务,并且这些异步操作是依赖前一个结果的(从一个接口取值再用结果去请求另一个接口)
1 | let p1 = function (val) { |
上方展示了此种情况的解决方法,如果存在更多的情况也可以链式调用
有趣的是catch
会返回一个resolved
状态的promise