十三. Promise

66 阅读11分钟

十三. Promise

13.1. 认识Promise

  • Es6引入的语法, js里面的一种异步编程的解决方案, 最常见场景就是网络请求,

  • Promise是一个,可以翻译成 承诺、许诺 、期约;

  • 在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor

    • 这个回调函数会被立即执行,并且会传入另外两个回调函数resolve、reject作为executor的参数;
    • 成功时;回调resolve函数,会执行Promise对象的then方法传入的回调函数;
    • 失败时;回调reject函数,会执行Promise对象的catch方法传入的回调函数;

13.2. 基本使用:

有异步请求代码时, 用promise包裹起来, 当数据请求成功时,调用resolve(), resolve会回调then方法传入的回调函数, 将请求结果传入then处理, 实现请求和处理代码的分离 ; 若请求失败,调用reject, reject会回调catch方法传入的回调函数, 将错误的信息传入catch处理

 // 在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor
 new Promise((resolve, reject)=>{
     setTimeout(()=> { 
         resolve('success message') // 数据请求成功; 回调resolve()
 ​
         // reject('err message') // 数据请求失败时;回调reject()
     },1000)
     // then方法传入的回调函数,会在Promise执行resolve函数时,被回调
 }).then((res) =>{
     console.log(res);  // success message
     // catch方法传入的回调函数,会在Promise执行catch函数时,被回调
 }).catch((err)=>{
     console.log(err); // err message
 })

13.3. 三种状态

状态一旦确定下来,那么就是不可更改的(锁定)

  • 待定(pending): 初始状态,既没有被兑现,也没有被拒绝;

    • 当执行executor中的代码时,处于该状态;
  • 已兑现(fulfilled): 意味着操作成功完成;

    • 执行了resolve时,处于该状态;
  • 已拒绝(rejected): 意味着操作失败;

    • 执行了reject时,处于该状态;

image-20211027015927948.png

13.4. Promise的resolve参数

  1. 如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数;
  2. resolve的参数如果是一个新Promise, 那么这个新Promise会决定原Promise的状态,相当于进行了状态移交
 const newPromise= new Promise((resolve, reject)=>{
     reject("err message") 
 })
 ​
 new Promise((resolve, reject)=>{
     resolve(newPromise) // 状态移交后,相当于这里是执行的reject("err message") 
 }).then(res=>{
     console.log(res);
 }).catch(err=>{
     console.log(err); // err message
 })

3. resolve的参数如果是一个对象,并且对象有实现then方法,那么会执行then方法,并且根据then方法的结果来决定Promise的状态:

 new Promise((resolve, reject)=>{
     const obj={
         then: function(resolve, reject){ reject('err message') }
     }
     resolve(obj)
 }).then(res=>{
     console.log(res);
 }).catch(err=>{
     console.log(err); // err message
 })

13.5. Promise的then方法

then方法是Promise对象上的一个方法:它其实是放在Promise的原型上的 Promise.prototype.then

  1. then方法可以接受两个参数:
  • fulfilled的回调函数:当状态变成fulfilled时会回调的函数;
  • reject的回调函数:当状态变成reject时会回调的函数;
 new Promise((resolve, reject)=>{
     setTimeout(()=>{
         // resolve('data')
         reject('err')
     },1000)
 }).then(
     data=>{
         console.log(data);
     },
     err=>{
         console.log(err);
     }
 )

2. 同一个Promise可以被多次调用then方法

  • 当我们的resolve方法被回调时,所有的then方法传入的回调函数都会被调用
 const promise = new Promise((resolve, reject) => {
     resolve('success message')
 })
 promise.then(res => {
     console.log('res1 ' + res); // res1 success message
 })
 promise.then(res => {
     console.log('res2 ' + res); // res2 success message
 })
 promise.then(res => {
     console.log('res3 ' + res); // res3 success message
 })

3. then方法- 返回值

  • then方法本身是有返回值的,它的返回值是一个Promise,所以我们可以进行如下的链式调用

    • 但是then方法返回的Promise到底处于什么样的状态呢?
  • Promise有三种状态,那么这个Promise处于什么状态呢?

    • 当then方法中的回调函数本身在执行的时候,那么它处于pending状态;

    • 当then方法中的回调函数返回一个结果时,那么它处于fulfilled状态,并且会将结果作为resolve的参数;

    • 注意如果是err回调函数return 一个值,那么这么值会作为resolve的参数

      • 情况一:返回一个普通的值(数值/字符串/对象/undefined);
      • 情况二:返回一个Promise;
      • 情况三:返回一个thenable值;
    • 当then方法抛出一个异常时,那么它处于reject状态;

3.1 情况一:

  • 如果我们返回的是一个普通值,那么这个普通值会被作为一个新的Promise的resolve值

    • 如果没写返回值,默认则返回undefined,undefined会被作为一个新的Promise的resolve值
     const promise = new Promise((resolve, reject) => {
         resolve('success message')
     })
     ​
     promise.then(res => {
         console.log(res); // success message
         return 1
     }).then(res=>{
         console.log(res); // 1
     }).then(res => {
         console.log(res); // undefined
     })
     // 上面等同下面代码
     // promise.then(res => {
     //     return new Promise((resolve) => {
     //         console.log(res);
     //         resolve(1)
     //     })
     // }).then(res => {
     //     console.log(res);
     // }).then(res => {
     //     console.log(res);
     // })
     ​
     // 注意:如果是err回调函数return 一个值,那么这么值会作为resolve的参数
     const promise = new Promise((resolve, reject) => {
         reject('err message')
     })
     ​
     promise.then(res => { 
         console.log('err1 ' + err); // err1 err message
         return 12
     }).then(res => {
         console.log('res2 ' + res); // res2 12
     }, err => {
         console.log('err2 ' + err);
     })
    

3.2 情况二:

  • 这里依然会创建一个新的Promise,只是新的Promise的resolve参数是旧的Promise,

  • 因为resolve参数是Promise,所以新Promise会决定原Promise的状态

     const promise = new Promise((resolve, reject) => {
         resolve('success message')
     })
      
     promise.then(res => {
         return new Promise((resolve)=>{
             setTimeout(() => {
                 resolve(111)
             },3000)
         })
     }).then(res => {
         console.log(res); // 111
     })
     // 上面如同下面的代码
     // const newPromise= new Promise(resolve=>{
     //     resolve(111)
     // })
     // promise.then(res => {
     //     return new Promise((resolve)=>{
     //         resolve (newPromise)
     //     })
     // }).then(res => {
     //     console.log(res); // 111
     // })
    

3.3 情况三:

  • resolve参数是对象且对象内实现了then方法,那么会执行then方法,并且根据then方法的结果来决定Promise的状态:

     const promise = new Promise((resolve, reject) => {
         resolve('success message')
     })
     ​
     promise.then(res => {
        // 这里还是返回一个新Promise,将对象放到resolve中    
         return { // return new Promise(resolve=>resolve(obj.then()))
             then: function (resolve, reject) {
                 resolve(111)
             }
         }
     }).then(res => {
         console.log(res); // 111
     })
    

13.6. Promise的catch方法

  1. 当executor抛出异常时,会调用catch方法传入的回调函数

    • catch方法其实是语法糖
     const promise = new Promise((resolve, reject) => { 
         throw new Error('err message')
     })
     ​
     promise.catch(err => {
         console.log(err); // err message
     }) 
     // catch方法是下面这种方式的语法糖
     // promise.then(undefined,err => {
     //     console.log(err); // err message
     // }) 
    
  2. catch的特殊性

    • 这里有两个Promise:原Promise / 返回的新Promise;那这里的catch方法哪个Promise的方法?

      • catch方法是优先捕获原Promise的异常,如果原Promise没有异常,则捕获新Promise的异常
      • 如果新的Promise也没有异常,则catch方法不执行
     const promise = new Promise((resolve, reject) => {
         reject('原Promise err message')
     })
     promise.then(res => {
         return 111
     }).catch(err => {
         console.log(err); // 原Promise err message
     })
    
     const promise = new Promise((resolve, reject) => { 
         resolve('success message')
     })
     promise.then(res => { 
         throw new Error('新Promise err message')
     }).catch(err => {
         console.log(err); // 新Promise err message
     })
    
  3. 拒绝捕获的问题

    • then和catch分开写就是两个方法了,不会相互影响
    • reject()执行时,会回调catch传入的回调参数,但then方法执行时,并没有处理捕获异常的方法
     const promise = new Promise((resolve, reject) => {
         reject('err message')
     })
     promise.then(res => {
         console.log(res);
     ​
     })
     promise.catch(err => {
         console.log(err);
     })
    
  4. catch方法- 返回值

    • 把catch的返回值,作为一个新的Promise的resolve值
     const promise = new Promise((resolve, reject) => {
         reject('err message')
     })
     promise.then(res => {
         console.log(res);
     }).catch(err => {
         return "aaaa" // 等同new Promise(resolve=>resolve('aaaa')
     }).then(res => {
         console.log(res); // aaa
     }).catch(err => {
         console.log(err);
     })
    

13.7. Promise的finally方法

finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是reject状态,最终都会被执行的代码。

finally方法是不接收参数的,因为无论前面是fulfilled状态,还是reject状态,它都会执行。

13.8. 类方法- resolve

前面的then、catch、finally方法都属于Promise的实例方法,都是存放在Promise的prototype上的。

下面我们再来看一下Promise的类方法。

  • 有时候我们已经有一个现成的内容了,希望将其转成Promise来使用,这个时候我们可以使用 Promise.resolve 方法来完成。
  • Promise.resolve的用法相当于new Promise,并且执行resolve操作:
 Promise.resolve({name: "why"})
 // 等同于
 // new Promise((resolve, reject)=>{
 //     resolve({ name: "why" })
 // })

13.9. 类方法- reject

  • reject方法类似于resolve方法,只是会将Promise对象的状态设置为reject状态。
  • Promise.reject的用法相当于new Promise,只是会调用reject:
  • Promise.reject传入的参数无论是什么形态,都会直接作为reject状态的参数传递到catch的
 Promise.reject({name: "why"}) 
 // 等同于
 // new Promise((resolve, reject)=>{
 //     reject({ name: "why" })
 // })

13.10. 类方法- all

  • all的作用是将多个Promise包裹在一起形成一个新的Promise;

  • 新的Promise状态由包裹的所有Promise共同决定:

    • 当所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有Promise的返回值 组成一个数组;
    • 当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数

需求:所有的Promise都变成fulfilled时,再拿结果

 const p1 = new Promise((resolve, reject) => {
     setTimeout(() => {
         resolve({
             name: 'kebi',
             age: 18
         });
     }, 2000)
 })
 const p2 = new Promise((resolve, reject) => {
     setTimeout(() => {
         resolve({
             name: 'wyh',
             age: 19
         });
     }, 1000)
 })
 ​
 // 如果传入数组的不是Promise,会自动将其转成Promise; Promise.resolve("aaa")
 Promise.all([p1, p2, "aaa"]).then((results) => {
     // results是一个数组,results[0]是上面的请求结果,results[1]是下面的请求结果
     // 0: {name: 'kebi', age: 18}
     // 1: {name: 'wyh', age: 19}
     // 3: 'aaa'
     console.log(results);
 })

13.11. 类方法- allSettled

all方法有一个缺陷:当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。

  • 那么对于resolved的,以及依然处于pending状态的Promise,我们是获取不到对应的结果的;

在ES11(ES2020)中,添加了新的API Promise.allSettled:

  • 该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是reject时,才会有最终的状态;
  • 并且这个Promise的结果一定是fulfilled的;

通过打印的结果可以知道:

  • allSettled的结果是一个数组,数组中存放着每一个Promise的结果,并且是对应一个对象的;
  • 这个对象中包含status状态,以及对应的value值;
 const p1 = new Promise((resolve, reject) => {
     setTimeout(() => {
         resolve({
             name: 'kebi',
             age: 18
         });
     }, 2000)
 })
 const p2 = new Promise((resolve, reject) => {
     setTimeout(() => {
         reject({
             name: 'wyh',
             age: 19
         });
     }, 1000)
 })
 ​
 // 如果传入数组的不是Promise,也会自动将其转成Promise 
 Promise.allSettled([p1, p2]).then((results) => {
     // [
     //     { status: 'fulfilled', value: { name: 'kebi', age: 18 } },
     //     { status: 'rejected', reason: { name: 'wyh', age: 19 } }
     // ]
     console.log(results);
 })

13.12. 类方法- race

如果有一个Promise有了结果,我们就希望决定最终新Promise的状态,那么可以使用race方法:

  • race是竞技、竞赛的意思,表示多个Promise相互竞争,谁先有结果(不管是fulfilled还是rejected),那么就使用谁的结果;
 const p1 = new Promise((resolve, reject) => {
     setTimeout(() => {
         resolve({
             name: 'kebi',
             age: 18
         });
     }, 2000)
 })
 const p2 = new Promise((resolve, reject) => {
     setTimeout(() => {
         resolve({
             name: 'wyh',
             age: 19
         });
     }, 1000)
 })
 ​
 Promise.race([p1, p2]).then((results) => {
     // { name: 'wyh', age: 19 }
     console.log(results);
 }).catch(err=>{
     console.log(err);
 })

13.13. 类方法- any

any方法是ES12中新增的方法,和race方法是类似的:

  • any方法会等到一个fulfilled状态,才会决定新Promise的状态;

  • 如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态;再回调catch,然后报一个AggregateError的错误。

    • AggregateError是个类,有个errors的属性
    • 所有的Promise都是reject时,可以通过err.errors拿到错误信息
 const p1 = new Promise((resolve, reject) => {
     setTimeout(() => {
         resolve({
             name: 'kebi',
             age: 18
         });
     }, 2000)
 })
 const p2 = new Promise((resolve, reject) => {
     setTimeout(() => {
         reject({
             name: 'wyh',
             age: 19
         });
     }, 100)
 })
 ​
 Promise.any([p1, p2]).then((results) => {
     // {name: 'kebi', age: 18}
     console.log(results);
 }).catch(err => {
     console.log(err.errors);
 })

13.14. 手写模拟Promise

 const PROMISE_STATUS_PENDING = 'pending'
 const PROMISE_STATUS_FULFILLED = 'fulfilled'
 const PROMISE_STATUS_REJECTED = 'rejected'
 // 工具函数:执行函数时捕获异常
 function exeFunctionWithCatchError(exeFn, value, resolve, reject) {
   try {
     const result = exeFn(value)
     resolve(result)
   } catch (error) {
     reject(error)
   }
 }
 class HYpromise {
   constructor(executor) {
     this.status = PROMISE_STATUS_PENDING
     this.onRejectedFns = []
     this.onFulfilledFns = []
 ​
     const resolve = (value) => {
       if (this.status === PROMISE_STATUS_PENDING) {
         // 添加微任务:本轮事件循环结束后就会执行
         queueMicrotask(() => {
           // 如果状态不是pending则终止执行
           if (this.status !== PROMISE_STATUS_PENDING) return
           this.status = PROMISE_STATUS_FULFILLED
           this.value = value
           // debugger
           this.onFulfilledFns.forEach(fn => {
             fn(this.value)
           })
 ​
         });
 ​
       }
     }
     const reject = (reason) => {
       if (this.status === PROMISE_STATUS_PENDING) {
         queueMicrotask(() => {
           if (this.status !== PROMISE_STATUS_PENDING) return
           this.status = PROMISE_STATUS_REJECTED
           this.reason = reason
           this.onRejectedFns.forEach(fn => {
             fn(this.reason)
           })
         })
       }
     }
 ​
     try {
       executor(resolve, reject)
     } catch (error) {
       reject(error)
     }
   }
   then(onFulfilled, onRejected) {
     onRejected = onRejected || (err => {
       throw err
     })
     onFulfilled = onFulfilled || (value => {
       return value
     })
     // 直接返回HYpromise,里面的代码还是会执行,
     // 而且可以得到onFulfilled, onRejected的返回值
     return new HYpromise((resolve, reject) => {
       // 如果then调用时,状态已经确定了,则直接调用对应的回调函数
       if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
         // 如果在res回调函数中抛出异常(throw new Error),则会调用err的回调函数
         //     try {
         //         const value = onFulfilled(this.value)
         //         resolve(value)
         //     } catch (error) {
         //         reject(error)
         //     }
         exeFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
 ​
       }
       if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
         exeFunctionWithCatchError(onRejected, this.reason, resolve, reject)
       }
       // 将成功的回调和失败的回调加到数组里
       if (this.status === PROMISE_STATUS_PENDING) {
         if (onFulfilled) this.onFulfilledFns.push(() => {
           // try {
           //     const value = onFulfilled(this.value)
           //     resolve(value)
           //     console.log(this);
 ​
           // } catch (error) {
           //     reject(error)
           // }
           exeFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
         });
         if (onRejected) this.onRejectedFns.push(() => {
           exeFunctionWithCatchError(onRejected, this.reason, resolve, reject)
 ​
         })
 ​
       }
     })
 ​
   }
   catch (onRejected) {
     return this.then(undefined, onRejected)
   };
   finally(onFinally) {
     this.then(() => {
       onFinally()
     }, () => {
       onFinally()
     })
   }
   static resolve(value) {
     return new HYpromise(resolve => resolve(value))
   }
   static reject(reason) {
     return new HYpromise((resolve, reject) => {
       reject(reason)
     })
   }
   static all(promises) {
     // 问题关键:什么时候执行resolve,什么时候执行reject
     return new HYpromise((resolve, reject) => {
       const values = []
       promises.forEach(p => {
         p.then(res => {
           // debugger
           values.push(res)
           console.log(values);
           if (values.length === promises.length) {
             resolve(values)
           }
         }, err => {
           reject(err)
         })
       })
     })
   }
   static allSettled(promises) {
     return new HYpromise(resolve => {
       const results = []
       promises.forEach(p => {
         p.then(res => {
           results.push({
             status: PROMISE_STATUS_FULFILLED,
             value: res
           })
           if (results.length === promises.length) {
             resolve(results)
           }
         }, err => {
           results.push({
             status: PROMISE_STATUS_REJECTED,
             value: err
           })
           if (results.length === promises.length) {
             resolve(results)
           }
         })
       })
     })
   }
   static race(promises) {
     new HYpromise((resolve, reject) => {
       promises.forEach(p => {
         p.then(res => {
           resolve(res)
         }, err => {
           reject(err)
         })
       })
     })
   }
   static any(promises) {
     // resolve:至少有一个请求成功才执行
     // reject:全部请求都失败了才执行
     const reasons = []
     return new HYpromise((resolve, reject) => {
       promises.forEach(p => {
         p.then(resolve, err => {
           reasons.push(err)
           if (reasons.length === promises.length) {
             reject(new AggregateError(reasons))
           }
         })
       })
 ​
     })
   }
 }
 ​
 ​
 ​
 const p1 = new HYpromise((resolve, reject) => {
   resolve(1111)
 })
 const p2 = new HYpromise((resolve, reject) => {
   reject(2222)
 })
 ​
 const p3 = new HYpromise((resolve, reject) => {
   resolve(3333)
 })
 ​
 HYpromise.race([p1, p2, p3]).then(res => {
   console.log(res);
 }).catch(err => {
   console.log(err);
 })
 // const promise = new HYpromise((resolve, reject) => {
 // reject('reject调用
 //     resolve('resolve调用')
 // })
 ​
 // HYpromise.resolve('静态resolve').then(res=>{
 //     console.log(res);
 // })
 ​
 // HYpromise.reject('静态reject').then(err=>{
 //     console.log(err);
 // })
 ​
 ​
 // promise.then(res => {
 //         console.log(res);
 //         return '555'
 //     }).catch(err => {
 //         console.log(err);
 //     })
 //     .finally(fanally => {
 //         console.log(fanally);
 //     })
 ​
 ​
 // promise.then(res => {
 //     console.log(res); 
 // }).catch(err => {
 //     console.log(err);
 // })
 ​
 // promise.then(res => {
 //     console.log('res1: ' + res);  
 // }, err => {
 //     console.log('err1: ' + err); 
 // }
 ​
 ​
 // promise.then(res => {
 //     console.log('res1: ' + res);
 //     //  throw new Error('456789')
 //     return 'cunba'
 // }, err => {
 //     console.log('err1: ' + err);
 //     // throw new Error('456789')
 // }).then(res => {
 //     console.log('res2 ' + res);
 // }, err => {
 //     console.log('err2: ' + err);
 // })
 ​
 // 延时调用then  多次调用
 // setTimeout(() => {
 //     promise.then(res => {
 //         console.log('res2: ' + res);
 //     }, err => {
 //         console.log('err2: ' + err);
 //     })
 // }, 1000);