深入理解Promise-下(完结篇)

141 阅读4分钟

努力让学习成为一种习惯,自信来源于充分的准备

如果你觉得该文章对你有帮助,欢迎大家点赞关注分享

前言

在上篇文章 深入理解Promise-中(透析篇)中我们实现了 Promise A+规范核心部分的 promise,但是相比于我们日常使用的ES6中的promise还少了些功能,这篇文章我们把其常用的功能都完善下

为了方便阅读,我把上篇文章中Promise实现的完整代码挪过来

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function isPromiseLike(obj) {
  return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}

class MyPromise {
  #state = PENDING
  #result = undefined
  #promiseHandler = []
  constructor(executor) {
    if (typeof executor !== 'function') {
      throw new Error('is not a function')
    }
    const resolve = (result) => {
      this.#statusChangeHandler(FULFILLED, result)
    }
    const reject = (result) => {
      this.#statusChangeHandler(REJECTED, result)
    }
    try {
      executor(resolve, reject)
    } catch(e) {
      reject(e)
    }
  }
  #statusChangeHandler(status, result) {
    if (this.#state !== PENDING) return
    this.#state = status
    this.#result = result
    this.#run()
  }
  #microTaskRun(cb) {
   if (typeof process === 'object' && typeof process.nextTick === 'function') {
     process.nextTick(cb)
   } else if (typeof MutationObserver === 'function') {
     const textNode = document.createTextNode('')
     const observer = new MutationObserver(cb)
     observer.observe(textNode, {
       characterData: true
     })
     textNode.data = 1
   } else {
    setTimeout(cb, 0);
   }
  }
  #run() {
    if (this.#state === PENDING) return
    while(this.#promiseHandler.length) {
      this.#thenableHandler(this.#promiseHandler.shift())
    }
  }
  #thenableHandler({onFulfilled, onRejected, resolve, reject}) {
    this.#microTaskRun(() => {
      let cb = this.#state === FULFILLED ? onFulfilled : onRejected
      if (typeof cb !== 'function') {
        cb = this.#state === FULFILLED ? () => this.#result : () => { throw this.#result }
      }
      try {
        const r = cb(this.#result)
        if (isPromiseLike(r)) {
          r.then(resolve, reject)
        } else {
          resolve(r)
        }
      } catch(e) {
        reject(e)
      }
    })
  }
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.#promiseHandler.push({
        onFulfilled,
        onRejected,
        resolve,
        reject
      })
      this.#run()
    })
  }
}

Promise.resolve/reject

class MyPromise {
  // ...
  static resolve(v) {
    if (v instanceof MyPromise) {
      return v
    } else if (isPromiseLike(v)) {
      return new MyPromise((resolve, reject) => {
        v.then(resolve, reject)
      })
    } else {
      return new MyPromise(resolve => {
         resolve(v)
      })
    }
  }
  static reject(v) {
     return new MyPromise((resolve, reject) => {
        reject(v)
     })
  }
}

// 测试例子
MyPromise.resolve(1).then(v => {
  console.log(v); // 1
})
const p = new MyPromise((resolve, reject) => {
  resolve(25)
})

MyPromise.resolve(p).then(v => {
  console.log(v); // 25
})

const pl = {
  then(resolve, reject) {
    reject(188)
  }
}

MyPromise.resolve(pl).then(null, v2 => {
  console.log(v2); // 188
})

MyPromise.reject(1).then(null, (v) => {
  console.log(v);
})

Promise.catch

class MyPromise {
  // ...
  catch(onRejected) {
    return this.then(null, onRejected)
  }
}

// 测试例子
const p = new MyPromise((resolve, reject) => {
  resolve(1)
})

p.then(v => {
  console.log(v); // 1
  throw 1
}).catch(err => {
  console.log(err); // 1
  return 11
}).then(v => {
  console.log(v); // 11
})

const p2 = MyPromise.reject(2)
p.then(v => {
  return p2
}).catch(err => {
  console.log(err) // 2
})

const p3 = MyPromise.reject(123)
MyPromise.resolve(p3).then().catch(e => {
  console.log(e); // 123
})

Promise.finally

class MyPromise {
  // ...
  finally(cb) {
    return this.then(cb, cb)
  }
}

Promise.all

function isIterable(obj) {
  return obj != null && typeof obj[Symbol.iterator] === 'function';
}

class MyPromise {
   // ...
   static all(promiseArr) {
    return new MyPromise((resolve, reject) => {
      // promiseArr数据类型校验 - 必须具有 Iterator 接口
      if(!isIterable(promiseArr)) {
        reject(new TypeError(`${promiseArr} is not iterable (cannot read property Symbol(Symbol.iterator))`))
        return
      }
      // 如果是可迭代对象,将其转化成数组。然后确保每一项都是Promise
      const _promiseArr = [...promiseArr].map(p => MyPromise.resolve(p))
      const len = _promiseArr.length
      if (len === 0) {
        resolve([])
        return
      }
      let resolveNum = 0
      const promiseResults = []
      _promiseArr.forEach((p, index) => {
        p.then(v => {
          // 这里需要注意用下标存储每个promise resolve的结果,而不要用push
          // 否则可能会造成输出结果的顺序和传入Promise顺序不一致
          promiseResults[index] = v
          // 只有所有的promise状态均为fulfilled,该promise的状态才会为fulfilled
          if (++resolveNum === len) {
            resolve(promiseResults)
          }
        },
        // 对于reject,只要其中一个被拒绝了,该promise的状态就会为rejected。所以直接传入即可
        //(因为promise的状态一旦修改便不可改变)
        reject
       )
      });
    })
  }
}

// 测试例子
const p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 0);
})
const p2 = MyPromise.resolve(12)
const p3 = MyPromise.resolve(13)

MyPromise.all([p1, p2, p3]).then(res => {
  console.log(res); // [1,12,13]
})
MyPromise.all([p1, p2, p3, MyPromise.reject(11)]).then(res => {
  console.log(res); // 不会执行
}).catch(err => {
  console.log(err) // 11
})

MyPromise.all([]).then(res => {
  console.log(res) // []
})

const myIterable = {
  *[Symbol.iterator]() {
      yield 1;
      yield 2;
      yield 3;
  }
};
MyPromise.all(myIterable).then(res => {
  console.log(res) // [1,2,3]
})

这里额外提一句,上面测试例子整体输出如下(具体原因涉及到事件循环了,这个后续会单独出篇文章讲解,这里只关注Promise功能本身。不关注多个Promise实例,代码的执行顺序)

image.png

Promise.any

Promise.anyPromise.all是镜像关系。当任何一个参数实例状态为fulfilled,包装实例的状态为fulfilled。当全部参数实例状态为rejected,包装实例的状态为fulfilled

class MyPromise {
  // ...
  static any(promiseArr) {
    return new MyPromise((resolve, reject) => {
      if(!isIterable(promiseArr)) {
        reject(new TypeError(`${promiseArr} is not iterable (cannot read property Symbol(Symbol.iterator))`))
        return
      }
      const _promiseArr = [...promiseArr].map(p => MyPromise.resolve(p))
      const len = _promiseArr.length
      if (len === 0) {
        reject(new AggregateError([]))
        return
      }
      let rejectNum = 0
      const reasons = []
      _promiseArr.forEach(p => {
        p.then(resolve, (reason) => {
          reasons.push(reason)
          if (++rejectNum === len) {
            reject(new AggregateError(reasons))
          }
        },
       )
      });
    })
  }
}

// 测试例子
MyPromise.any([MyPromise.reject(2), MyPromise.reject(2)]).catch(err => {
  console.log(err); // AggregateError: All promises were rejected
})

MyPromise.any([1, MyPromise.reject(2)]).then(res => {
  console.log(res) // 1
})

Promise.allSettled

class MyPromise {
  // ...
  static allSettled(promiseArr) {
    return new MyPromise((resolve, reject) => {
      if(!isIterable(promiseArr)) {
        reject(new TypeError(`${promiseArr} is not iterable (cannot read property Symbol(Symbol.iterator))`))
        return
      }
      const _promiseArr = [...promiseArr].map(p => MyPromise.resolve(p))
      const len = _promiseArr.length
      if (len === 0) {
        resolve([])
        return
      }
      let num = 0
      const allResults = []
      _promiseArr.forEach((p, index) => {
        p.then(value => {
          allResults[index] = {
            status: FULFILLED,
            value
          }
        }, (reason) => {
          allResults[index] = {
            status: REJECTED,
            reason
          }
        }).finally(() => {
          if (++num === len) {
            resolve(allResults)
          }
        })
      });
    })
  }
}

// 测试例子
MyPromise.allSettled([1,2,MyPromise.reject(1)]).then(res => {
  console.log(res)
  /* [
      { status: 'fulfilled', value: 1 },
      { status: 'fulfilled', value: 2 },
      { status: 'rejected', reason: 1 } 
    ]
  */
})

Promise.race

class MyPromise {
  // ...
  static race(promiseArr) {
    return new MyPromise((resolve, reject) => {
      if(!isIterable(promiseArr)) {
        reject(new TypeError(`${promiseArr} is not iterable (cannot read property Symbol(Symbol.iterator))`))
        return
      }
      const _promiseArr = [...promiseArr].map(p => MyPromise.resolve(p))
      for (const p of _promiseArr) {
        p.then(resolve, reject)
      }
    })
  }
}

// 测试例子
const p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 100);
})
const p2 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    reject(2)
  }, 200);
})

MyPromise.race([p1, p2]).then(res => {
  console.log(res); // 1
}).catch(err => {
  console.log(err);
})

整体完整版(es6版本)

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

function isPromiseLike(obj) {
  return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}

function isIterable(obj) {
  return obj != null && typeof obj[Symbol.iterator] === 'function';
}

class MyPromise {
  #state = PENDING
  #result = undefined
  #promiseHandler = []
  constructor(executor) {
    if (typeof executor !== 'function') {
      throw new Error('is not a function')
    }
    const resolve = (result) => {
      this.#statusChangeHandler(FULFILLED, result)
    }
    const reject = (result) => {
      this.#statusChangeHandler(REJECTED, result)
    }
    try {
      executor(resolve, reject)
    } catch(e) {
      reject(e)
    }
  }
  #statusChangeHandler(status, result) {
    if (this.#state !== PENDING) return
    this.#state = status
    this.#result = result
    this.#run()
  }
  #microTaskRun(cb) {
   if (typeof process === 'object' && typeof process.nextTick === 'function') {
     process.nextTick(cb)
   } else if (typeof MutationObserver === 'function') {
     const textNode = document.createTextNode('')
     const observer = new MutationObserver(cb)
     observer.observe(textNode, {
       characterData: true
     })
     textNode.data = 1
   } else {
    setTimeout(cb, 0);
   }
  }
  #run() {
    if (this.#state === PENDING) return
    while(this.#promiseHandler.length) {
      this.#thenableHandler(this.#promiseHandler.shift())
    }
  }
  #thenableHandler({onFulfilled, onRejected, resolve, reject}) {
    this.#microTaskRun(() => {
      let cb = this.#state === FULFILLED ? onFulfilled : onRejected
      if (typeof cb !== 'function') {
        cb = this.#state === FULFILLED ? () => this.#result : () => { throw this.#result }
      }
      try {
        const r = cb(this.#result)
        if (isPromiseLike(r)) {
          r.then(resolve, reject)
        } else {
          resolve(r)
        }
      } catch(e) {
        reject(e)
      }
    })
  }
  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      this.#promiseHandler.push({
        onFulfilled,
        onRejected,
        resolve,
        reject
      })
      this.#run()
    })
  }
  catch(onRejected) {
    return this.then(null, onRejected)
  }
  finally(cb) {
    return this.then(cb, cb)
  }
  static resolve(v) {
    if (v instanceof MyPromise) {
      return v
    } else if (isPromiseLike(v)) {
      return new MyPromise((resolve, reject) => {
        v.then(resolve, reject)
      })
    } else {
      return new MyPromise(resolve => {
         resolve(v)
      })
    }
  }
  static reject(v) {
    return new MyPromise((resolve, reject) => {
      reject(v)
    })
  }
  static all(promiseArr) {
    return new MyPromise((resolve, reject) => {
      // promiseArr数据类型校验 - 必须具有 Iterator 接口
      if(!isIterable(promiseArr)) {
        reject(new TypeError(`${promiseArr} is not iterable (cannot read property Symbol(Symbol.iterator))`))
        return
      }
      // 如果是可迭代对象,将其转化成数组。然后确保每一项都是Promise
      const _promiseArr = [...promiseArr].map(p => MyPromise.resolve(p))
      const len = _promiseArr.length
      if (len === 0) {
        resolve([])
        return
      }
      let resolveNum = 0
      const promiseResults = []
      _promiseArr.forEach((p, index) => {
        p.then(v => {
          // 这里需要注意用下标存储每个promise resolve的结果,而不要用push
          // 否则可能会造成输出结果的顺序和传入Promise顺序不一致
          promiseResults[index] = v
          // 只有所有的promise状态均为fulfilled,该promise的状态才会为fulfilled
          if (++resolveNum === len) {
            resolve(promiseResults)
          }
        },
        // 对于reject,只要其中一个被拒绝了,该promise的状态就会为rejected。所以直接传入即可
        //(因为promise的状态一旦修改便不可改变)
        reject
       )
      });
    })
  }
  static any(promiseArr) {
    return new MyPromise((resolve, reject) => {
      if(!isIterable(promiseArr)) {
        reject(new TypeError(`${promiseArr} is not iterable (cannot read property Symbol(Symbol.iterator))`))
        return
      }
      const _promiseArr = [...promiseArr].map(p => MyPromise.resolve(p))
      const len = _promiseArr.length
      if (len === 0) {
        reject(new AggregateError([]))
        return
      }
      let rejectNum = 0
      const reasons = []
      _promiseArr.forEach(p => {
        p.then(resolve, (reason) => {
          reasons.push(reason)
          if (++rejectNum === len) {
            reject(new AggregateError(reasons))
          }
        },
       )
      });
    })
  }
  static allSettled(promiseArr) {
    return new MyPromise((resolve, reject) => {
      if(!isIterable(promiseArr)) {
        reject(new TypeError(`${promiseArr} is not iterable (cannot read property Symbol(Symbol.iterator))`))
        return
      }
      const _promiseArr = [...promiseArr].map(p => MyPromise.resolve(p))
      const len = _promiseArr.length
      if (len === 0) {
        resolve([])
        return
      }
      let num = 0
      const allResults = []
      _promiseArr.forEach((p, index) => {
        p.then(value => {
          allResults[index] = {
            status: FULFILLED,
            value
          }
        }, (reason) => {
          allResults[index] = {
            status: REJECTED,
            reason
          }
        }).finally(() => {
          if (++num === len) {
            resolve(allResults)
          }
        })
      });
    })
  }
  static race(promiseArr) {
    return new MyPromise((resolve, reject) => {
      if(!isIterable(promiseArr)) {
        reject(new TypeError(`${promiseArr} is not iterable (cannot read property Symbol(Symbol.iterator))`))
        return
      }
      const _promiseArr = [...promiseArr].map(p => MyPromise.resolve(p))
      for (const p of _promiseArr) {
        p.then(resolve, reject)
      }
    })
  }
}

最后

本篇文章,我们进一步完善了Promise,手写了es6中的Promise各种方法。其实难度并不大。相信跟着到这里的小伙伴对Promise内部的各种原理都有了比较清晰的了解甚至完全掌握了。但是有一个点在文章中提过一嘴,没有展开讲。那就是多个Promise实例间整体代码的执行顺序。这就涉及到事件循环了,后续会专门出一篇文章😄

到这里,就是本篇文章的全部内容了

如果你觉得该文章对你有帮助,欢迎大家点赞关注分享

如果你有疑问或者出入,评论区告诉我,我们一起讨论