乐趣区

关于前端:深入理解Promise

Promise 的前提概念

Promise 是一个构造函数,用来生成 Promise 实例
Promise 构造函数承受一个函数作为参数,该函数有两个参数,别离是 resolvereject
resolve:胜利时的回调
reject:失败时的回调

Promise 别离有三个状态
1、pending:进行中
2、fulfilled:已胜利
3、rejected:已失败

Promise 的执行
Promise 一旦新建后就会立马执行,无奈中途勾销,Promise返回的只有两种状态,并且该状态产生就无奈更改。也就是说 Promise 的状态只有以下两种状况:
1、从 pendingfulfilled
2、从 pendingrejected
pending的状态一旦扭转,就不会再发生变化。

Promise 的后果返回
Promise 外部后果一旦返回,resolvereject 其中的任何一种,即可产生回调。
1、resolve的回调在 then() 中获取 (胜利的回调)
2、reject 的回调在 cathc() 中获取(失败,捕捉谬误)

let promise = new Promise(function(resolve,reject){
     // sth...
     let i = 5;
     if(i > 1){resolve(i) // 胜利
     } else {reject(error) // 失败
     }
})
    
promise.then(el=>{
     // el 即为 resolve 的参数
     console.log(el);  // 5
    })
    .catch(err=>{console.log(err);
    })

Promise 的执行程序
Promise 创立后会立马执行,then()是一个异步回调,将在所有的同步工作执行完后执行 then(),也就是说:Promise 在新建的时候会立刻执行,then()catch() 为异步工作,当所有同步工作执行完后才会执行then()

let pro = new Promise(function(resolve,reject){console.log('Promise 新建');
    resolve()})

pro.then(el=>{console.log('.then 回调');
})
console.log('同步工作');

上述代码的执行后果:
1、Promise 新建
2、同步工作
3、then() 回调


Promise 参数和执行程序

resolve函数的参数出了能够携带正常值以外,还能够携带 Promise 实例

let pro = new Promise(function(resolve,reject){
let i = 10
    if(i < 50){resolve(i)
    } else {reject(error)
   }
})

let pro2 = new Promise(function(resolve,reject){resolve(pro)  // 参数为上一个 Promise 实例
})
      
pro2.then(el=>{console.log(el);  // 10
    })
    .catch(err=>{console.log(err);
    })

执行程序:
先执行 pro,如果pro 的状态是 pending 时,pro2则会期待执行,当 pro 的状态变成 resolverejected时,pro2则会立马执行,因为 pro 决定 pro2 的状态,所以前面的 then 变成了针对pro


Promise 的嵌套执行

let p = new Promise((resolve,reject)=>{
     let i = 10
     if(i > 5){resolve(i)
     } else {reject('pro 谬误')
     }
})

let p2 = new Promise((resolve,reject)=>{
    p.then(el=>{console.log(el * 2);  // 20
        if(el > 6){// 此处须要写成 return resolve(el)看起来会更失常
           resolve(el)
           console.log('我是 p1 的.then 回调');  // 我是 p1 的.then 回调
        } else {reject('pro2 谬误')
        }
    })
   })
   
let p3 = new Promise((resolve,reject)=>{
    p2.then(el=>{if(el > 50){resolve(el)
        } else {reject('pro3 谬误')
        }
    })
})

多个 Promise 的执行程序:
新建 Promise 后会立马执行,先执行 Promise 内的工作,执行实现后再执行 then()
如果 Promise 内有其它 Promisethen()工作执行(这里简称 p2),会先将p2then()工作执行结束,再执行本身的 then() 工作
多个 Promise 并行,会先按程序执行 Promise 的工作,再别离依照程序别离执行他们的 then() 工作

须要 留神 的是:下面 p2Promse中,失常来说,resolvereject 执行完后,Promise的使命应该实现,但此处因为执行程序的问题,并没有先执行 resolve,而是执行了console 的工作,因该写成 return resolve(value) 的模式,让 Promise 更失常一些,否则会呈现不必要的意外。


Promise.prototype.then()

then办法是定义在原型对象 Promise.prototype 上的,它能够为 Promise 实例增加状态扭转时的回调函数。
then有两个参数(可选):
resolved:胜利状态的回调函数
rejected:失败状态的回调函数
then 返回的是一个新的 Promise 实例,因而能够采纳链式写法,then()前面还能够接then()

let p = new Promise((resolve,reject)=>{
    let i = 5;
    if(i > 4){resolve(i)
    } 
})

p.then(el=>{console.log('then1:'+ el);
    if(el > 3){return el}
}).then(el=>{console.log('then2:'+ el * 2);
}).catch(err=>{console.log(err);
}
 /*
   输入后果:then1:5
   then2:10
*/

下面代码应用 then 办法,第一个 then() 回调函数执行完后,会将返回后果传入第二个回调函数,也就是说,第二个 then() 的参数是第一个 then() 返回的后果。
catch的捕捉具备冒泡性质,也就是说,后面不管 .then 了多少次,外面产生的谬误,都会被最初一个 catch 捕捉到。


Promise.prototype.catch()

Promise.prototype.catch()办法是 .then(null,rejection) 的别名,也就是说,catch其实是用来指定产生谬误时的回调函数。

let p = new Promise((resolve,reject)=>{reject('错误信息')
   })
   
p.then(el=>{// sth...}).catch(err=>{console.log(err);  // 错误信息
  })

上述代码中,p办法返回 Promise 对象,该对象返回的是 rejectthen() 办法调用回调函数,then()有两个参数,一个 resolvedrejected,如果 Promise 返回的是 reject,那么.then 的状态就会变成 rejectedrejected 会调用 catch() 办法指定的回调函数,解决该谬误,catch的参数就是 reject 抛出的错误信息。

如果 then 运行中抛出谬误,也会被 catch()捕捉

let p2 = new Promise((resolve,reject)=>{resolve(100)
   })
p2.then(el=>{throw new Error('then 抛错')
}).catch(err=>{console.log(err);  // Error: then 抛错
})

catch 捕获谬误的形式

   // 形式一
let promise = new Promise(function(resolve,reject){
    try {throw new Error('形式一谬误')
    } catch(e){reject(e)
    }
})

promise.catch(err=>{console.log(err);  //  形式一谬误
   })
// 形式二
let promise2 = new Promise(function(resolve,reject){reject(new Error('形式二谬误'))
})

promise2.catch(err=>{console.log(err);  //  形式二谬误
   })
// 形式三
let promise3 = new Promise((resolve,reject)=>{reject('形式三谬误')
   })
   
promise3.catch(err=>{console.log(err);  // 形式三谬误
   })

以上三种成果是一样的,前两种通过 Error 抛出异样,后一种间接抛出异样
catch 返回的也是一个 Promise 对象,前面还能够接着调用 then() 办法
如果 Promise 的状态变成 resolved,那么再抛出谬误是有效的,因为Promise 的状态一旦产生扭转,就会永恒放弃该状态。

执行阻塞问题
Promise 外部谬误不会影响到到异步队列中的 Promise 内部代码,同步代码还是会造成阻塞

let p3 = new Promise((resolve,reject)=>{resolve(str)  // ReferenceError: str is not defined
})

p3.then(el=>{console.log(el)})
setTimeout(()=>{console.log('Promise 内部代码')},1000)  // Promise 内部代码
console.log('同步代码');  // 未打印

Promise在建设时会立刻执行,此处报错了,所以前面的同步代码都无奈执行,然而异步的 setTimeout 还是会执行,并不受影响。


finally()

finally有最初、终于的意思,也就是说 finally() 办法不论 Promise 最初的状态如何,都会执行

let p = new Promise((resolve,reject)=>{resolve('东方不败')
})
p.then(el=>{console.log(el);  // 东方不败
})
.catch(err=>{console.log(err);
})
.finally(()=>
    console.log('finally 执行')  // finally 执行
)

上述代码中,不论 Promise 最初的状态怎么样,finally都会执行,即便没有 then()catch()finally也会执行

须要留神的是,finally不承受任何参数,然而 finally 的执行程序跟外部是否是一个函数无关,如果 finally 外部是一个函数,那么 finally 会在 Promise 最初执行,如果 finally 外部没有任何函数,间接输入,那么 finally 会在 Promise 最后面执行。

finally的执行跟 Promise 的状态无关。


Promise.all()

Promise.all()办法用于将多个 Promise 实例包装成一个新的 Promise 实例
语法:let p = Promise.all([p1,p2,p3])

let p = new Promise((resolve,reject)=>{resolve(10)
})
p.then(el=>{console.log(el);
})
        
let p2 = new Promise((resolve,reject)=>{// resolve(20)
    reject(20)
})
p2.then(el=>{console.log(el);
})
.catch(err=>{console.log('p2:',err);})

// p、p2 都返回 resolve 则触发该函数
Promise.all([p,p2])
       .then(el=>{console.log(el);
        })
        .catch(err=>{console.log('all:',err);})  // all:20

Promise.all 的两个 promise 参数状态都变成 fulfilled 时,才会触发 Promise.all() 的办法,如果其中以一个参数被 rejected,那么Promise.all() 也会变成 rejected 的状态,会被 catch 捕获。
下面代码中,如果 p2 没有 chach 办法,则会调用 Promise.all()catch


race()

Promise.race()办法能够将多个 Promise 实例包装成一个新的 Promise 实例。
语法:let p = Promise.race([p1,p2,p3])

let p = new Promise((resolve,reject)=>{setTimeout(()=>{resolve('p1 状态')
      },2000)
    })
    
let p2 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve('p2 状态')
       },1000)
     })
     
let p3 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve('p3 状态')
       },3000)
     })
     
let p4 = new Promise((resolve,reject)=>{setTimeout(()=>{reject('reject:p4 状态')
        },4000)
      })

let promise = Promise.race([p,p2,p3,p4])
promise
.then(el=>{console.log(el);  // p2 状态
})
.catch(err=>{console.log(err);
})

race有竞争,角逐的意思,该办法其实就是参数中,哪一个 Promise 的状态先产生扭转,就调用先产生扭转的 promise,这个el 就是最先产生扭转的 promise 的返回值。


Promise.allSettled()

Promise.allSettled()办法能够在所有异步操作都完结的状况下,调用该办法。它与 Promise.all() 办法不同的是,all()办法须要所有异步都胜利才会进入 then(),而allSettled() 不须要。

let p = new Promise((resolve,reject)=>{resolve('p1 状态')
})
        
let p2 = new Promise((resolve,reject)=>{setTimeout(()=>{reject('p2 状态')
       },3000)
})

Promise.allSettled([p,p2])
.then(el=> console.log(el) )

Promise.allSettled()的参数不论是 reject 还是 resolve 都会触发,只有参数的状态全都产生扭转即可,下面代码中,p2提早了 3s 状态才产生扭转,那么 Promise.allSettled() 则会期待参数的状态全都产生扭转时触发。


Promise.any()

Promise.any()办法承受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。

let p = new Promise((resolve,reject)=>{resolve('p1 状态')
})
        
let p2 = new Promise((resolve,reject)=>{reject('p2 状态')
})

Promise.any([p,p2])
       .then(el=>{console.log('then:',el);  // then: p1 状态
       })
       .catch(err=>{console.log('catch:',err);
       })

只有有一个参数的状态变成 fulfilledPromise.any 就会变成 fulfilled 状态
如果所有的参数实例都变成 rejected 状态,包装实例才会变成 rejected 状态
也就是说,any是只有有一个参数胜利,any就能够 then,所有参数都rejectany 才会catch


Promise.resolve()

Promise.resolve()能够将现有对象转为 Promise 对象,Promise.resolve()办法就起到这个作用。
语法:let p = Promise.resolve(obj)

let p = Promise.resolve('obj')
p.then(el=> console.log(el))  // obj

// 其实相当于
let p2 = new Promise(resolve=>resolve('obj'))

如果 Promise.resolve() 没有参数,那么将间接返回 resolved 状态的 Promise 对象,并且是一个没有参数的 then() 办法。

let p3 = Promise.resolve()
p3.then(el=>{console.log(el);  // undefined
})


setTimeout(()=>{console.log('定时器');
},0)

Promise.resolve()
       .then(()=>{setTimeout(()=>{console.log('resolve 内定时器')},0)
         console.log('resolve 外部');
        })
console.log('内部');

Promise的执行程序,Promise是一个异步的微工作,同步工作执行完后才会执行异步工作,异步的微工作会先执行,微工作执行完后才会执行异步的宏工作,此处的 setTimeout 是异步的宏工作,所以会最初执行,内部的定时器是异步的宏工作,Promise外部的定时器是异步微工作内的宏工作,所以微工作内的宏工作会更慢执行,此处的执行程序为:内部、resolve外部、定时器、resolve内定时器。


Promise.reject()

Promise.reject()返回一个新的 Promise 实例,该实例的状态为rejected

let p = Promise.reject('错误信息')
p.catch(err=>console.log(err))

// 等同于
let p2 = new Promise((resolve,reject)=>{reject('错误信息 2')
})
p2.catch(err=>console.log(err))

Promise.try()

理论开发中,会遇到这么一种状况,不论函数是同步还是异步的,都想用 Promise 来解决它,因为这样就能够用 then() 指定下一步流程,catch解决抛出的谬误,更便于管理流程。
基于 Promise 咱们能够用 async 做到同步的代码格调执行异步操作,当初有一个最新的提案,应用 Promise.try() 来捕捉所有的同步和异步谬误。
为什么这么做?
Promise抛出的谬误,catch能够捕捉到,但此处仅限于以后 Promise 的异样谬误,也就是异步的。如果同步谬误抛出,那么 catch() 是捕获不到的,须要在外层再嵌套一层try...catch,这样写起来十分麻烦,代码如下:

try {let p = new Promise((resolve,reject)=>{reject('异步谬误')
       })
   p.catch(err=> void console.log(err) )  // 异步谬误
   throw Error('同步谬误')
      
} catch (e) {console.log(e);  // 同步谬误
}
// 这样 catch 两次,能够解决同步和异步的抛错问题

如果应用 Promise.try() 就能够捕捉所有的同步和异步谬误

Promise.try(()=>{let p2 = new Promise((resolve,reject)=>{reject('异步谬误 2')
    })    
         throw Error('同步谬误 2')
    })
    .then(el=>{console.log('then:',el);
     })
    .catch(err=>{console.log('catch:',err);
    })

此处的 Promise.try 就相当于 try 代码块,Promise.catch就相当于 catch 代码块。


案例源码:https://gitee.com/wang_fan_w/es6-science-institute

如果感觉这篇文章对你有帮忙,欢送点亮一下 star 哟

退出移动版