你不知道的Promise

206 阅读4分钟

Promise

promise的原理

promise是一个构造函数,会接受一个函数,这个回调会传入resolve和reject两个函数,分别控制pending的状态结果value和reason。 promise的返回是一个包含then的函数。通过调用reject、resolve改变promise的状态fulfilled、rejected状态。在then函数的传入的回调函数中传参会得到上个promise对象的resolve或reject的结果。

Promise为什么使用了发布订阅模式

因为resolve之前,then已经执行过了,我们要提前收集起来,然后在状态改变后推入微任务队列然后执行。

then的实现原理是什么?, 为什么可以链式调用?

在程序执行过程中,首先会以发布订阅收集所有的then函数中的onFulfilled或onRejected,如果此时promise状态是pending状态push进入成功或失败的队列, then一旦监听到resolved | rejected状态,那么此时resolve或reject执行能获取到value | reason的值(也就是上一次的结果,在promise返回后能在onFulfilled获取到value)。在then的返回值一般有promise的情况、方法和对象、死循环、基本数据类型通过resolvePromise处理。then会传入onFulfilled和onRejected方法。一旦上一级的promise执行了resolve,会立即执行onFulfilledArray队列里面的所有promise对象,这个执行的时机发生在微任务。

then传入的回调函数返回值不同情况的判断

  1. 基本数据类型
  2. 死循环
  3. Promise
  4. object和function

resolvePromise的规范

resolvePromise(promise2, x, resolve, reject)

  • 如果 promise2 和 x 相等,那么 reject error;

  • 如果 x 是一个 promise

    • 如果 x 是一个 pending 状态,那么 promise 必须要在 pending, 直到 x 变成 fulfilled or rejected
    • 如果 x 被 fulfilled, fulfill promise with the same value
    • 如果 x 被 rejected, reject promise with the same reason
  • 如果 x 是一个 object 或者 function

    • let thenable = x.then
    • 如果 x.then 这一步出错,那么 reject promise with e as the reason
    • 如果 then 是一个函数,then.call(x, resolvePromiseFn, rejectPromise) resolvePromiseFn 的 入参是 y, 执行 resolvePromise(promise2, y, resolve, reject); rejectPromise 的 入参是 r, reject promise with r. 如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。 如果调用then抛出异常e 如果 resolvePromise 或 rejectPromise 已经被调用,那么忽略 则,reject promise with e as the reason 如果 then 不是一个function. fulfill promise with x.

手写个promise

const resolvePromise = (promise2, result, resolve, reject) => {
  // 死循环,结果和then的返回promise相等
  if (result === promise2) {
    reject(new TypeError('error due to circular reference'))
  }

  // 是否已经执行过 onfulfilled 或者 onrejected
  let consumed = false
  let thenable

  if (result instanceof LPromise) {
    if (result.status === 'pending') {
      result.then(function(data) {
        //promsie状态改变后,resolvePromise返回结果
        resolvePromise(promise2, data, resolve, reject)
      }, reject)
    } else {
      //promise状态已改变
      result.then(resolve, reject)
    }
    return
  }

  let isComplexResult = target => (typeof target === 'function' || typeof target === 'object') && (target !== null)

  // 如果返回的是疑似 Promise 类型
  if (isComplexResult(result)) {
    try {
      thenable = result.then
      // 如果返回的是 Promise 类型,具有 then 方法
      if (typeof thenable === 'function') {
        thenable.call(result, function(data) {
          //保证执行一次
          if (consumed) {
            return
          }
          consumed = true

          return resolvePromise(promise2, data, resolve, reject)
        }, function(error) {
          //保证执行一次
          if (consumed) {
            return
          }
          consumed = true

          return reject(error)
        })
      }
      else {
        resolve(result)
      }
    } catch(e) {
      if (consumed) {
        return
      }
      consumed = true
      return reject(e)
    }
  }
  else {
    //基本数据类型
    resolve(result)
  }
}

function LPromise(execute) {
  this.status = 'pending'
  this.value = null
  this.reason = null

  this.onFulfilledArray = []
  this.onRejectedArray = []

  const resolve = (value) => {
    setTimeout(() => {
      if(this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'
        this.onFulfilledArray.forEach(func => func(value))
      } 
    }, 0);
  }
  const reject = (reason) => {
    setTimeout(() => {
      if(this.status === 'pending') {
        this.reason = reason
        this.status = 'rejected'
        this.onRejectedArray.forEach(func => func(reason))
      } 
    }, 0);
  }

   execute(resolve, reject)
}

LPromise.prototype.then = function(onFulfilled, onRejected) {
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : data => data 

  onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }

  let promise2

  if(this.status === 'fulfilled') {
    return promise2 = new LPromise((resolve, reject) => {
      setTimeout(() => {
        try {
          let result = onFulfilled(this.value)
          resolve(result)
        } catch(e) {
          reject(e);
        }
      });
    })
  }

  if(this.status === 'rejected') {
    return promise2 = new LPromise((resolve, reject) => {
      setTimeout(() => {
        try {
          let result = onRejected(this.reason)
          resolve(result)
        } catch(e) {
          reject(e);
        }
      });
    })
  }

  if(this.status === 'pending') {
     return promise2 = new LPromise((resolve, reject) => {
      this.onFulfilledArray.push(() => {
        try {
          let result = onFulfilled(this.value)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)          
        }
      })

      this.onRejectedArray.push(() => {
        try {
          let result = onRejected(this.reason)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)          
        }
      })
     })
  }
}

module.exports = LPromise

Promise各个api的实现

function myPromiseAll(promises) {
  let arr = [], count = 0
  return new Promise((resolve, reject) => {
    promises.forEach((promise, index) => {
      Promise.resolve((res) => {
        arr[index] = res
        count++
        if(count === promise.length) resolve(arr)
      }).catch((e) => {
        reject(e)
      })
    })
  })
}

Promise.resolve = function(value) {
  if(value instanceof Promise) {
    return value
  }

  return new Promise(resolve => resolve(value))
}

Promise.reject = function(reason) {
  return new Promise((resolve, reject) => reject(reason))
}

//返回迭代的数组中第一个reject | fulfilled包装后的实例
Promise.race = function(promiseArr) {
  return new Promise((resolve, reject) => {
    promiseArr.forEach(p => {
      Promise.resolve(p).then(val => {
        resolve(val)
      }).catch((e) => {
        reject(e)
      })
    })
  })
}

//allSettled 用数组记录对应的状态和值
Promise.allSettled = function(promiseArr) {
  let result = []

  return new Promise((resolve, reject) => {
    promiseArr.forEach((p, i) => {
      Promise.resolve(p).then(val => {
        result.push({
          status: 'fulfilled',
          value: val
        })
        if(result.length === promiseArr.length) {
          resolve(val)
        }
      }, err => {
        result.push({
          status: 'rejected',
          reason: err
        })
        if(result.length === promiseArr.length) {
          resolve(val)
        }
      })
    })
  })
}

//Promise.all 无论如何返回第一个完成状态,如果是空数组或全部都是rejected状态的,就返回AggregateError错误  

Promise.any = function(promiseArr) {
  let index = 0
  return new Promise((resolve, reject) => {
    if(promiseArr.length === 0) return
    promiseArr.forEach((p, i) => {
      Promise.resolve(p).then(val => {
        resolve(val)
      }, (err) => {
        index++
        if(index === i) {
          reject(new AggregateError('all promise were rejected'))
        }
      })
    })
  })
}

Promise如何并发 | 顺序执行

const promiseArrGenerator = (num) => new Array(num).fill(0).map((item, index) => () => {
  return  new Promise((resolve) => { setTimeout(() => {
    resolve(index)
 }, Math.random() * 100)})
})

const proArr = promiseArrGenerator(100)

// Promise.all(proArr.map(fn => fn())).then(res => console.log(res)) 

//如何顺序执行
// proArr.forEach(fn => fn().then(res => console.log(res)))

//用reduce实现
const promiseReduce = (proArr) => {
  proArr.reduce((prePromise, nextPromise) => {
    return prePromise.then((res) => {
      res && console.log(res);
      return nextPromise()
    })
  }, Promise.resolve(-1))
}
// promiseReduce(proArr)

设计一个并发的pipe, 如chrome浏览器最大的6个并发限制

const promisePipe = (proArr,  pipeNums) => {
  if(pipeNums > proArr.length) {
    Promise.all(proArr.map(fn => fn())).then(res => console.log(res))
  }

  let _arr = [...proArr]

  for (let i = 0; i < pipeNums; i++) {
    run(_arr.shift())
  }

  function run(promiseFn) {
    promiseFn().then((res) => {
      console.log(res);
      if(_arr.length) run(_arr.shift())
    })
  }
}
promisePipe(proArr, 1)

Promise一旦启动,就无法停止,那么如何实现一个abort方法来停止

const controller = new AbortController()
const signal = controller.signal

function abortPromise(proArr) {
  let flag = true
  signal.addEventListener('abort', () => {
      console.log('信号终止');
      flag = false
  })
   proArr.forEach(fn => {
      fn().then(res => {
        if(!flag) {
          throw new Error('abort')
        } else {
          console.log(res);
        }
      })
   })
}
abortPromise(proArr)

setTimeout(() => {
  controller.abort()
}, 5000);