全面学习Promise

134 阅读9分钟

Promise

异步请求

function requestData(url) {
  setTimeout(() => {
    if (url === 'zs') {
      let names = ['abc', 'cba', 'nba']
      return names
    } else {
      let errMessage = '请求失败,url错误'
      return errMessage
    }
  }, 3000)
}

console.log(requestData('zs'));

因为setTimeout是异步的,所有上面的输出的是undefined,那我们是怎么解决这个问题的呢?

我们是通过传入回调函数来解决的

function requestData(url, successCallback, failCall) {
  setTimeout(() => {
    if (url === 'zs') {
      let names = ['abc', 'cba', 'nba']
      successCallback(names)
    } else {
      let errMessage = '请求失败,url错误'
      failCall(errMessage)
    }
  }, 3000)
}

requestData('zs', (res) => { console.log(res); }, (err) => { console.log(err); })

这种回调有很多的弊端:

  1. 如果我们自己封装的requestData,那么我们在封装的时候必须要自己设计好callback名称,并且使用好
  2. 如果我们使用的是第三方库或者别人封装的,那么我们必须去看别人的源码或文档才知道这个函数需要怎么去获取到结果

基本使用

Promise是一个类,在通过new插件Promise对象时,我们需要传入一个回调函数,我们称之为executor

  • 这个函数会被立即执行,并且给传入另外两个回调函数resolve、reject
  • 当我们调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数
  • 当我们调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数
function requestData(url, successCallback, failCall) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url === 'zs') {
        let names = ['abc', 'cba', 'nba']
        resolve(names)
      } else {
        let errMessage = '请求失败,url错误'
        reject(errMessage)
      }
    }, 3000)
  })
}

const promise = requestData('zs')
// 第一种写法 这种写法在node中会报错
// promise.then(() => {
//   console.log('请求成功');
// })
// promise.catch(() => {
//   console.log('请求失败');
// })

// 第二种写法
promise.then((res) => { console.log(res); }, (err) => { console.log(err); })

// 第三种写法 比较常用
promise.then((res) => {
  console.log(res);
}).catch((err) => {
  console.log(err);
})

三大状态

pending状态:待定/悬而未决的状态

fulfilled状态:调用resolve进入此状态(已敲定/兑现状态)

rejected状态:调用reject进入此状态(已拒绝状态)

new Promise((resolve, reject) => {
  // pending状态:待定/悬而未决的状态
  console.log('------');
  // resolve()  // 处于fulfilled状态(已敲定/兑现状态)
  reject()  // 处于rejected状态(已拒绝状态)
}).then(res => {
  console.log('res:', res);
}, err => {
  console.log('err:', err);
})

resovle参数

  1. 普通的值或对象

  2. 传入一个Promise,那么当前的Promise的状态会由传入的Promise来决定,相当于状态进行了移交

    const newPromise = new Promise((resolve, reject) => {
      resolve('aaa')
    })
    
    new Promise((resolve, reject) => {
      resolve(newPromise)
    }).then(res => {
      console.log('res:', res);
    }, err => {
      console.log('err:', err);
    })
    // 输出aaa
    
  3. 传入一个对象,并且这个对象有实现then方法(thenable),那么也会执行该then方法,并且由该then方法决定后续状态

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

对象方法

then方法

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

console.log(Object.getOwnPropertyDescriptors(Promise.prototype));

同一个then方法可以多次调用

const promise = new Promise((resolve, reject) => {
  resolve('hahaha')
})

promise.then(res => {
  console.log('res1:', res);
})

promise.then(res => {
  console.log('res2:', res);
})

promise.then(res => {
  console.log('res3:', res);
})

then方法可以传递的回调函数有返回值

// then方法本身也是有返回值的,它的返回值是Promise
// 1.如果我们返回的是一个普通值,那么这个普通的值被作为一个新的Promise的resolve值
promise.then(res => {
  return 'aaa'
  // 相当于 return new Promise((resolve)=>{
  //   resolve('aaa')
  // })
})

// 这里的第二个then的时候已经不是原来的promise了,而是第一个then返回的promise
promise.then(res => {
  return 'aaa'
  // 如果没有返回值默认返回一个undefined 因为then是一个函数,函数没有返回值就会返回一个undefined
}).then(res => {
  console.log('res:', res);
  return 'bbb'
})

// 2.如果我们返回的是一个promise 那么当前的Promise的状态会由传入的Promise来决定,相当于状态进行了移交
promise.then(res => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(111)
    }, 3000)
  }).then(res => {
    console.log('res:', res);
  })
})

// 3.如果返回的是一个对象,并且返回对象实现了thenable
promise.then(res => {
  return {
    then: function (resolve, reject) {
      resolve(222)
    }
  }
  // 相当于 new Promise(resolve => resolve(obj.then))
}).then(res => {
  console.log('res:', res);
})

catch方法

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

catch方法捕获的是第一个promise的错误,如果第一个方法没有错误,catch留着来捕获第二个方法的错误

const promise = new Promise((resolve, reject) => {
  // reject('rejected status')
  throw new Error('rejected status')
})

// 1.当executor抛出异常的时候,我们也是可以使用错误捕获的回调函数的
promise.then(undefined, (err) => {
  console.log("err:", err);
  console.log('----------');
})

// 2.可以通过catch方法来传入错误(拒绝)捕获的回调函数
promise.catch(err => {
  console.log('err:', err);
})
// 但是上面的用法不符合promise/a+规范 所以我们大部分的使用下面这种写法
// 这里面有两个promise,一个是原本的promise,另一个是then方法new出来的promise,但是catch方法捕获的是第一个promise的错误,如果第一个方法没有错误,catch留着来捕获第二个方法的错误
promise.then(res => {
  return 111
}).catch(err => {
  console.log('err:', err);
  console.log('----------');
})

catch方法的返回值,catch方法的返回值依然是放到了一个新的promise中,返回值并不是一个错误,所有依然会在之后的then中调用

const promise = new Promise((resolve, reject) => {
  // resolve('111')
  reject('111')
  // throw new Error('rejected status')
})

promise.then(res => {
  console.log('res:', res);
}).catch(err => {
  console.log('err:', err);
  return 'catch return value'
}).then(res => {
  console.log('res result:', res);
}).catch(err => {
  console.log('err result:', err);
})

finally方法

finally是在ES9(ES2018)中新增的一个特性:表示Promise对象无论是在fullfilled还是在rejected状态都会被执行的代码

finally方法是不接收参数的,也是一个在对象上的方法

console.log(Object.getOwnPropertyDescriptors(Promise.prototype));
const promise = new Promise((resolve, reject) => {
  resolve('resolve')
})

promise.then(res => {
  console.log('res:', res);
}).catch(err => {

}).finally(() => {
  console.log('finally');
})

类方法

resolve

// 1.传入普通的值
const promise = Promise.resolve({ name: 'zs' })
// 相当于
// const promise2 = new Promise((resolve, reject) => {
//   resolve({ name: 'zs' })
// })

// 2.传入promise
const promise = Promise.resolve(new Promise((resolve, reject) => {
  resolve('111')
}))

promise.then(res => {
  console.log('res:', res);
})

// 3.传入一个thenable对象

reject

注意:reject不分三种情况,所有传入含thenable对象或者new Promise都是一样的,它会把你传入的数据直接打印

// const promise = Promise.reject('reject message')
// 相当于
// const promise2 = new Promise((resolve, reject) => {
//   reject('reject message')
// })

// const promise = Promise.reject({
//   then: function (resolve, reject) {
//     resolve("111")
//   }
// })

const promise = Promise.reject(new Promise(() => { }))

promise.then(res => {
  console.log('res:', res);
}).catch(err => {
  console.log('err:', err); // err: reject message  err: { then: [Function: then] }   err: Promise { <pending> }
})

all

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(111)
  }, 1000)
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve(222)
    reject(222)
  }, 2000)
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(333)
  }, 3000)
})

// 需求:所有的Promise都变成fulfilled时,再拿到结果
// 意外:再拿到所有结果之前,有一个promise变成了reject,那么整个promise是rejected
Promise.all([p1, p2, p3, 'aaa']).then(res => {
  console.log(res); // [ 111, 222, 333, 'aaa' ]
}).catch(err => {
  console.log('err:', err);
})

allSetled

all方法有一个缺陷:当其中的一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态,那么依然处于pending状态的Promise,我们是获取不到对应的结果的

为了解决这个问题,ES11(2020)中,添加了新的API Promise.allSettled

  • 这个方法会在所有的Promise都有结果(settled),无论是fulfilled,还是rejected时,才会有最终的状态
  • 并且这个Promise的结果一定是fulfilled
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(111)
  }, 1000)
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve(222)
    reject(222)
  }, 2000)
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(333)
  }, 3000)
})

// 需求:所有的Promise都变成fulfilled时,再拿到结果
// 意外:再拿到所有结果之前,有一个promise变成了reject,那么整个promise是rejected
Promise.allSettled([p1, p2, p3]).then(res => {
  console.log(res);
}).catch(err => {
  console.log('err:', err);
})

// 返回
[
  { status: 'fulfilled', value: 111 },
  { status: 'rejected', reason: 222 },
  { status: 'fulfilled', value: 333 }
]

race

race是竞赛的意思,也就是所只要有一个Promise变成了fulfilled状态,那么就结束,当然有一个为rejected状态时也会结束,只要有结果就返回

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(111)
  }, 2000)
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve(222)
    reject(222)
  }, 1000)
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(333)
  }, 3000)
})

// 只要有一个Promise变成了fulfilled状态,那么就结束
Promise.race([p1, p2, p3]).then(res => {
  console.log(res);
}).catch(err => {
  console.log('err:', err);
})

any

any方法是一个ES12新增的方法,与race类似

  • any方法会等到一个fulfilled状态,才会决定新的Promise状态,也就是第一个为rejected状态时,不会结束
  • 如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(111)
  }, 2000)
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // resolve(222)
    reject(222)
  }, 1000)
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(333)
  }, 3000)
})

// 只要有一个Promise变成了fulfilled状态,那么就结束
Promise.any([p1, p2, p3]).then(res => {
  console.log(res); // 111
}).catch(err => {
  console.log('err:', err);
})

// 如果全是reject
Promise.any([p1, p2, p3]).then(res => {
  console.log(res); 
}).catch(err => {
  // console.log('err:', err);  // [AggregateError: All promises were rejected] (AggregateError是一个类,可以使用new AggregateError创建出来)
  console.log('err:', err.errors);  // err: [ 111, 222, 333 ]
})

手写Promise

const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'

// 工具函数
function execFunctionWithCatchError(execFn, value, resolve, reject) {
  try {
    const result = execFn(value)
    resolve(result)
  } catch (err) {
    reject(err)
  }
}

class myPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING
    this.value = undefined
    this.reason = undefined
    this.onFulfilledFns = []
    this.onRejectedFns = []

    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return
          this.status = PROMISE_STATUS_FULFILLED
          this.value = value
          // then传递的第一个回调函数
          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
          // then传递的第二个回调函数
          this.onRejectedFns.forEach(fn => {
            fn(this.reason)
          })
        })
      }
    }

    try {
      executor(resolve, reject)
    } catch (err) {
      reject(err)
    }
  }

  then(onFulfilled, onRejected) {
    onRejected = onRejected || (err => { throw err })
    onFulfilled = onFulfilled || (res => { return res })

    return new myPromise((resolve, reject) => {
      // 1.如果再then调用的时候,状态已经确定下来
      if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
        execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
      }
      if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
        execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
      }

      // 2.将成功的回调添加到数组中
      if (this.status === PROMISE_STATUS_PENDING) {
        if (onFulfilled) this.onFulfilledFns.push(() => {
          execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
        })
        if (onRejected) this.onRejectedFns.push(() => {
          execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
        })
      }
    })
  }

  catch(onRejected) {
    return this.then(undefined, onRejected)
  }

  finally(onFinally) {
    this.then(() => {
      onFinally()
    }, () => {
      onFinally()
    })
  }

  static resolve(value) {
    return new myPromise((resolve) => resolve(value))
  }

  static reject(reason) {
    return new myPromise((resolve, reject) => reject(reason))
  }

  static all(promises) {
    return new myPromise((resolve, reject) => {
      const values = []
      promises.forEach(promise => {
        promise.then(res => {
          values.push(res)
          if (values.length === promises.length) {
            resolve(values)
          }
        }, err => {
          reject(err)
        })
      })
    })
  }

  static allSettled(promises) {
    return new myPromise((resolve) => {
      const results = []
      promises.forEach(promise => {
        promise.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) {
    return new myPromise((resolve, reject) => {
      promises.forEach(promise => {
        promise.then(res => {
          resolve(res)
        }, err => {
          reject(err)
        })
      })
    })
  }

  static any(promises) {
    const reasons = []
    return new myPromise((resolve, reject) => {
      promises.forEach(promise => {
        promise.then(resolve, err => {
          reasons.push(err)
          if (reasons.length === promises.length) {
            reject(new AggregateError(reasons))
          }
        })
      })
    })
  }
}

// 测试
const promise = new myPromise((resolve, reject) => {
  console.log('状态pending');

  resolve(111)
  // reject(222)
  // throw new Error('executor err message')
})

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(111)
  }, 1000)
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(222)
  }, 2000)
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(333)
  }, 3000)
})

myPromise.any([p1, p2, p3]).then(res => {
  console.log(res);
}).catch(err => {
  console.log('err:', err);
})