浅谈理解
Promise A+(手绘简略版)
详细参考:https://promisesaplus.com/
1 | var obj1 ={ |
promise本质 の 个人理解
一个promise实例可以看作一个容器,里面会维护状态,异步操作结果,异步操作后成功回调, 异步操作后失败回调
- status: string 内部状态(可能是pending, resolved, rejected)
- value: any 异步操作的结果
- succCb: Array[Function] 成功的回调函数数组,通过调用then的时候进行绑定添加,在状态变成resolve的时候执行
- failCb: Array[Function] 失败的回调函数数组,通过调用then的时候进行绑定添加,在状态变成reject的时候执行
Promise构造函数执行发生了什么?
Promise构造函数, 内部定义一个resolve方法 和reject方法,用于修改promise实例的状态/值,和执行回调函数,
构造函数执行的逻辑是,将预先定义好的这两个方法作为参数传入去调用实例化promise传入的参数,即函数fn,即 实例化的时候执行fn(resolve, reject)
fn函数中有用户自定义逻辑,在不同时机调用resolve方法或reject方法修改容器内部状态和值等。
Promise原型上的then方法发生了什么?
Promise原型上的then方法用于 同步代码执行的时候向Promise实例绑定回调函数。当Promise实例状态resolved的时候,执行回调。
then中的回调执行后返回的Promise是什么状态?
根据Promise/A+ 规范后的理解,假设x是回调执行的返回值
1 正常执行函数,x=可能没返回(undefined)或者返回值(字符串,数字,非promise的一般对象), resolve(x)
2 正常执行函数,x= 返回promsie , 调用这个promise的then 方法, 循环1.3步判断
3 发生异常, 中间抛出异常,reject(e),中间任何异常,调用链不会往下执行
1 | //对比 下面 |
1 | var a= new Promise((x,y)=>{y(123)}).then(console.log('call then'),e=>{return e}) |
如果then中的回调返回的是promise呢?
如果then中不传回调,默认成功回调是v => v, 失败会抛出错误,是 e => {throw e}
返回的是promise执行链的话,以 最终最后的的promise 的状态和值作为then返回的promise的状态和值。
1 | var example = Promise.resolve(1).then(v=>{return Promise.resolve(100)}) |
promise吃掉异常和处理
1 | var example = new Promise((r1,r2)=>{console.log(abc)}) |
建议
如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。一般来说,不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。上面代码中,第二种写法要好于第一种写法,理由是第二种写法可以 捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch方法,而不使用then方法的第二个参数。一般总是建议,Promise 对象后面要跟catch方法,这样可以处理 Promise 内部发生的错误。
简单实现一个Promise
代码如下
1 | const PENDING ='pending' |
一道有趣的面试题
实现如下machine函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function machine() {
}
machine('ygy').execute()
// start ygy
machine('ygy').do('eat').execute();
// start ygy
// ygy eat
machine('ygy').wait(5).do('eat').execute();
// start ygy
// wait 5s(这里等待了5s)
// ygy eat
machine('ygy').waitFirst(5).do('eat').execute();
// wait 5s
// start ygy
// ygy eat
1 | function machine(name){ |
语法
Promise构造函数
用new实例化一个promise, 参数是一个executor函数,executor 函数在Promise构造函数返回新建对象前被调用。
这个函数接收2个函数做参数resolve和reject, 在这个executor函数内部做一些操作,然后调用resovle(Promise状态从pending变成resolved),或者调用reject(Promise状态从pending变成rejected),内部抛出异常(Promise状态从pending变成rejected,executor函数的返回值被忽略)
reject和resolve的时候可以传入参数
Promise原型上方法
Promise.prototype.catch(onRejected)
添加一个拒绝回调(rejection)到当前promise,返回一个新的promise。当这个回调函数被调用,新promise以它的返回值来resolve,否则如果当前promise进入fulfilled状态,则以当前promise的完成结果作为新promise的完成结果。Promise.prototype.then(onFulfilled, onRejected)
添加解决(fulfillment)和拒绝(rejection)回调到当前 promise, 返回一个新的 promise, 将以回调的返回值来resolve.Promise.prototype.finally(onFinally)
添加一个事件处理回调于当前promise对象,并且在原promise对象解析完毕后,返回一个新的promise对象。回调会在当前promise运行完毕后被调用,无论当前promise的状态是完成(fulfilled)还是失败(rejected)
Promise构造函数静态方法
Promise.all(iterable)
该promise对象在iterable参数对象里所有的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则立即触发该promise对象的失败。
这个新的promise对象在触发成功状态以后,会把一个包含iterable里所有promise返回值的数组作为成功回调的返回值,顺序跟iterable的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。1
2
3
4var arr=new Array(3).fill(new Promise((resolve,reject)=>{resolve(123)}))
var res = Promise.all(arr)
res.then((x)=>{console.log(x)}) // Promise {<resolved>: Array(3)}
// [123, 123, 123]Promise.race(iterable)
当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象1
2
3
4
5
6
7
8var p1= new Promise((resolve,reject)=>{
setTimeout(reject,1000,992)
})
var p2= new Promise((resolve,reject)=>{
setTimeout(resolve,2000)
})
var res = Promise.race([p1,p2])
console.log(res) //Promise {<rejected>: 992}Promise.resolve(value)
返回一个状态由给定value决定的Promise对象。Promise.reject(reason)
返回一个状态为失败的Promise对象,并将给定的失败信息传递给对应的处理方法
特征
一个 Promise 就是一个代表了异步操作最终完成或者失败的对象。Promise 本质上是一个绑定了回调的对象,而不是将回调传进函数内部。
- | 1 | 2 | 3 | 4 |
---|---|---|---|---|
特点 | 链式调用 | 异常处理,catch捕获前面所有new Promise()和then的异常,捕获后返回一个Promise | 回调异步, then中的回调函数被置入了一个微任务队列,不是立即执行 | |
注意点 | 避免嵌套promise | catch终止promise链 | 回调中return XX作为下一个promise resolve的值 | - |
分析
当resolve的值是一个promise?
reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例,
一个异步操作的结果是返回另一个异步操作,状态会被传递1
2
3
4
5
6
7var p1 = new Promise((resolve,reject)=>{
reject('123')
})
var p2 = new Promise((resolve,reject)=>{
resolve(p1)
})
// Promise {<rejected>: "123"}
then 返回了什么?
返回一个新的 promise, 将以回调的返回值(默认是undefiend)来resolve1
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
34var a = new Promise((resolve, reject) => {
resolve('123');
})
.then((x) => {
return new Promise((resolve, reject)=>{resolve('789')})
.then(()=> new Promise((resolve, reject)=>{resolve('100')}) )
})
console.log(a) // Promise {<resolved>: "100"}
var a = new Promise((resolve, reject) => {
resolve('123');
})
.then((x) => {
return new Promise((resolve, reject)=>{resolve('789')})
})
console.log(a) //Promise {<resolved>: "789"}
var a = new Promise((resolve, reject) => {
resolve('123');
})
.then((x) => {
return '456'
})
console.log(a) //Promise {<resolved>: "456"}
var a = new Promise((resolve, reject) => {
resolve('123');
})
.then(() => {
})
console.log(a) // Promise {<resolved>: undefined}
then 中函数被放到微任务队列
一个已经变成 resolve 状态的 Promise,传递给 then 的函数也总是会被异步调用:1
2
3
4
5
6
7
8
9var a = new Promise((resolve, reject) => {
resolve();
console.log('123')
})
.then((x) => {
console.log('456')
})
console.log('789')
// 123 789 456
1 | const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); |
分析:
- 执行wait函数,返回1个promise, 将第一个seTimeout放入宏任务队列
- Promise.resolve()执行完返回1个promise, then中回调() => console.log(2)放入微任务队列
- 执行同步代码console.log(1)
- 执行微任务队列中() => console.log(2),返回1个promise,将这个promise的then中回调放入微任务队列() => console.log(3)
- 执行微任务队列中的() => console.log(3)
- 执行宏任务队列中的setTimeout, 返回1个promise, 将这个promise的then中的回调() => console.log(4)放入微任务队列
- 执行微任务队列,() => console.log(4)
场景 + demo
旧式回调 API 中创建 Promise
理想状态下,所有的异步函数都已经返回 Promise 了。但有一些 API 仍然使用旧式的被传入的成功或者失败的回调。典型的例子就是setTimeout()函数。
幸运的是我们可以用 Promise 来包裹它。最好的做法是将有问题的函数包装在最低级别,并且永远不要再直接调用它们:1
2
3const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
wait(10000).then(() => saySomething("10 seconds")).catch(failureCallback);
多个异步操作依赖上一步的值
注意:如果想要在回调中获取上个 Promise 中的结果,上个 Promise 中必须要返回结果。因为上文提到then中回调执行会返回1个新promise,以回调返回值resolve。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15new Promise((resolve, reject) => {
resolve('123');
})
.then((x) => {
console.log(x);
return '456'
})
.then((x)=>{
console.log(x)
})
.catch(() => {
console.log('Something wrong');
})
// 123
// 456
避免嵌套
1 | doSomethingCritical() |
简便的 Promise 链式编程最好保持扁平化,不要嵌套 Promise,嵌套经常会是粗心导致的。
嵌套 Promise 是一种可以限制 catch 语句的作用域的控制结构写法。明确来说,嵌套的 catch 仅捕捉在其之前同时还必须是其作用域的 failureres,而捕捉不到在其链式以外或者其嵌套域以外的 error。如果使用正确,那么可以实现高精度的错误修复。
这个内部的 catch 语句仅能捕获到 doSomethingOptional() 和 doSomethingExtraNice() 的失败,而且还是在moreCriticalStuff() 并发运行以后。重要提醒,如果 doSomethingCritical() 失败,这个错误才仅会被最后的(外部)catch 语句捕获到。