promise深入学习

浅谈理解

Promise A+(手绘简略版)

详细参考:https://promisesaplus.com/

1
2
3
4
5
6
var obj1 ={
name:'helianthus',
then:(resolvePromise,rejectPromise)=>{resolvePromise(100)}
}

var p1 = Promise.resolve(1).then(()=>obj1) //Promise {<resolved>: 100}

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
2
3
4
5
6
7
8
9
//对比 下面
var a = Promise.resolve(1).then(v=>new Error('wrong'))
//状态resolved, 返回异常 , Value: Error: wrong at Promise.resolve.then.

var a = Promise.resolve(1).then(v=>{throw new Error('inner error'); return 123})
//状态reject, 抛出异常 , Value: Error: inner error at Promise.resolve.then.

var a = Promise.resolve(1).then(v=>{console.log(abc)})
// 状态reject, 执行报错,Value:ReferenceError: abc is not defined
1
2
3
4
var a= new Promise((x,y)=>{y(123)}).then(console.log('call then'),e=>{return e})
// 相当于传入的onFullfilled是个undefined参数
// 打印: call then
// 状态: resolved, value: 123

如果then中的回调返回的是promise呢?

如果then中不传回调,默认成功回调是v => v, 失败会抛出错误,是 e => {throw e}
返回的是promise执行链的话,以 最终最后的的promise 的状态和值作为then返回的promise的状态和值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var example  = Promise.resolve(1).then(v=>{return Promise.resolve(100)}) 
// Promise {<resolved>: 100}
var example = Promise.resolve(1).then(v=>{return Promise.resolve(100).then(v=>200).then(v=>300)})
// Promise {<resolved>: 300}
var example = Promise.resolve(1).then(v=>{return Promise.reject('innerError')})
// Promise {<rejected>: "innerError"}

var example = Promise.resolve(1).then(v=>{return Promise.resolve(100).then(throw new Error('innerError'))})
// Promise {<rejected>: "innerError"}
var example = Promise.resolve(1).then(v=>{return Promise.reject(new Error('innerError')).then()})
// Promise {<rejected>: Error: innerError}

var example = Promise.resolve(1).then(v=>{return Promise.reject(new Error('innerError')).then(v=>100,e=>200)})
// Promise {<resolved>: 200}

var example = Promise.resolve(1).then(v=>{return Promise.reject(new Error('innerError')).then(v=>100,e=>200).then(v=>{throw new Error('second error')}).then(v=>v, e=>400)})
// Promise {<resolved>: 400}

promise吃掉异常和处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var example = new Promise((r1,r2)=>{console.log(abc)})
// Promise {<rejected:ReferenceError: abc is not defined at Promise >

var example = new Promise((r1,r2)=>{console.log(abc)}).then(v=>v,e=>100)
// Promise {<resolved>: 100}

// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});

// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});

建议
如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。一般来说,不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。上面代码中,第二种写法要好于第一种写法,理由是第二种写法可以 捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch方法,而不使用then方法的第二个参数。一般总是建议,Promise 对象后面要跟catch方法,这样可以处理 Promise 内部发生的错误。


简单实现一个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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
const PENDING ='pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'


function MyPromise(fn){
let _this = this // 因为是异步操作所以先缓存this
_this.value = null
_this.status = PENDING
_this.succCb = []
_this.failCb = []

if (typeof fn !== 'function'){
throw new Error('argument must be a function')
}

function resolve(v){
console.log('call resolve')
if(_this.status === PENDING){ // // 只有pending的时候才能改变状态,状态变更, 获得value, 修改状态,执行回调
_this.value = v
_this.status = RESOLVED
_this.succCb.forEach(cb=>cb(_this.value))
}
}

function reject(e){
if(_this.status === PENDING){ // 状态resolve, 获得value, 修改状态,执行回调
_this.value = e
_this.status = REJECTED
_this.failCb.forEach(cb=>cb(_this.value))
}
}

try{
console.log('call fn')
fn(resolve, reject)
}catch(e){
reject(e)
}

}

MyPromise.prototype.then = function(onFullFilled, onRejected){
console.log('call then')

let _this= this

onFullFilled = typeof onFullFilled === 'function'? onFullFilled: v => v
onRejected = typeof onRejected === 'function'? onRejected: r => {throw e}

if(_this.status === PENDING){
_this.succCb.push(onFullFilled)
_this.failCb.push(onRejected)
}

if(_this.status === RESOLVED){
onFullFilled(_this.value)
}

if(_this.status === REJECTED){
onRejected(_this.value)
}
}

new MyPromise((resolve, reject)=>{resolve(123)}).then(v=>{console.log(v)})
// call fn
// call resolve
// call then
// 123

一道有趣的面试题

实现如下machine函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function 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
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
function machine(name){
return new Machine(name)
}

function Machine(name){
this.name = name
this.actions = []
this.init()
}
Machine.prototype.init = function(){
this.actions.push(`start ${this.name}`)
}

Machine.prototype.do = function(action){
this.actions.push(`${this.name} ${action}`)
return this
}
Machine.prototype.execute = async function(){
for(let item of this.actions){
let seconds = item.startsWith('wait')?item.match(/\d+/g)*1000:0
console.log(item)
await new Promise((resolve)=>{setTimeout(resolve, seconds, item)})
}
}

Machine.prototype.wait = function(seconds){
this.actions.push(`wait ${seconds}s`)
return this
}

Machine.prototype.waitFirst = function(seconds){
this.actions.unshift(`wait ${seconds}s`)
return this
}

语法

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
    4
    var 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
    8
    var 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
7
var p1 = new Promise((resolve,reject)=>{
reject('123')
})
var p2 = new Promise((resolve,reject)=>{
resolve(p1)
})
// Promise {<rejected>: "123"}

then 返回了什么?

返回一个新的 promise, 将以回调的返回值(默认是undefiend)来resolve

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
var 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
9
var a = new Promise((resolve, reject) => {
resolve();
console.log('123')
})
.then((x) => {
console.log('456')
})
console.log('789')
// 123 789 456

1
2
3
4
5
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

wait().then(() => console.log(4));
Promise.resolve().then(() => console.log(2)).then(() => console.log(3));
console.log(1); // 1, 2, 3, 4

分析:

  1. 执行wait函数,返回1个promise, 将第一个seTimeout放入宏任务队列
  2. Promise.resolve()执行完返回1个promise, then中回调() => console.log(2)放入微任务队列
  3. 执行同步代码console.log(1)
  4. 执行微任务队列中() => console.log(2),返回1个promise,将这个promise的then中回调放入微任务队列() => console.log(3)
  5. 执行微任务队列中的() => console.log(3)
  6. 执行宏任务队列中的setTimeout, 返回1个promise, 将这个promise的then中的回调() => console.log(4)放入微任务队列
  7. 执行微任务队列,() => console.log(4)

场景 + demo

旧式回调 API 中创建 Promise

理想状态下,所有的异步函数都已经返回 Promise 了。但有一些 API 仍然使用旧式的被传入的成功或者失败的回调。典型的例子就是setTimeout()函数。
幸运的是我们可以用 Promise 来包裹它。最好的做法是将有问题的函数包装在最低级别,并且永远不要再直接调用它们:

1
2
3
const 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
15
new 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
2
3
4
5
6
doSomethingCritical()
.then(result => doSomethingOptional()
.then(optionalResult => doSomethingExtraNice(optionalResult))
.catch(e => {console.log(e.message)})) // 即使有异常也会忽略,继续运行;(最后会输出)
.then(() => moreCriticalStuff())
.catch(e => console.log("Critical failure: " + e.message));// 没有输出

简便的 Promise 链式编程最好保持扁平化,不要嵌套 Promise,嵌套经常会是粗心导致的。

嵌套 Promise 是一种可以限制 catch 语句的作用域的控制结构写法。明确来说,嵌套的 catch 仅捕捉在其之前同时还必须是其作用域的 failureres,而捕捉不到在其链式以外或者其嵌套域以外的 error。如果使用正确,那么可以实现高精度的错误修复。
这个内部的 catch 语句仅能捕获到 doSomethingOptional() 和 doSomethingExtraNice() 的失败,而且还是在moreCriticalStuff() 并发运行以后。重要提醒,如果 doSomethingCritical() 失败,这个错误才仅会被最后的(外部)catch 语句捕获到。