Promise出现的原因我们不在赘述,不过是避免回调地狱之类的问题,下面我们就直接开始!请认真读完并理解每一行代码!


一、从零开始,手写Promise

1、Promise/A+

我们想要手写一个Promise,就要遵循Promise/A+规范,业界所有Promise的类库都遵循这个规范

其实Promise/A+规范对如何实现一个符合标准的Promise类库已经阐述的很详细了。每一行代码在Promise/A+规范中都有迹可寻,所以在下面实现的过程中,尽可能的将代码和Promise/A+规范一一对应起来。

2、基础版Promise

回顾一下最简单的Promise“使用”方式

  1. const p1 = new Promise((resolve,reject)=>{
  2. console.log('create a promise')
  3. resolve('成功了')
  4. })
  5. console.log('after new promise')
  6. const p2 = p1.then(data =>{
  7. console.log(data)
  8. throw new Error('失败了')
  9. })
  10. const p3 = p2.then(data=>{
  11. console.log('success',data)
  12. },err=>{
  13. console.log('faild',err)
  14. })

控制台输出:
"create a promise"
"after new promise"
"成功了"
"faild Error: 失败了"

  • 首先我们在调用Promise时,会返回一个Promise对象
  • 构建Promise对象时,需要传入一个executor函数,Promise的主要业务流程都在executor函数中执行
  • 如果运行在executor函数中的业务执行成功了,会调用resolve函数,如果执行失败了,则调用reject函数
  • Promise的状态不可逆,同事调用resolve函数和reject函数,默认会采取第一次调用的结果

以上简单介绍了Promise的一些主要的使用方法,结合Promise/A+规范,我们可以分析出Promise一下的基本特征:

  1. Promise有三个状态:pending、fulfilled、rejected
  2. new Promise时,需要传递一个executor()执行器,执行器立即执行
  3. executor接受两个参数,分别是resolve和reject;
  4. Promise的默认状态是pending;
  5. Promise有一个value保存成功状态的值,可以是undefined/promise
  6. promise有一个reason保存失败状态的值
  7. promise只能从pending到rejected,或者充pending到fulfilled,状态一旦确定,就不会再更改
  8. promise必须有一个then方法,then接受两个参数,分别是promise成功的回调onFulfilled,和promise失败的回调onRejected
  9. 如果调用then时,promise已经失败,那么执行onRejected,参数时promise的reason
  10. 如果调用then时已经成功,那么执行onFulfilled,参数时promise的value
  11. 如果then中抛出了异常,那么就会把这个一场作为参数,传递给下一个then的失败的回调onRejected

按照上面的特征,我们来写一下

  1. //三个状态
  2. const PENDING = 'PENDING'
  3. const FULFILLED = 'FULFILLED'
  4. const REJECTED = 'REJECTED'
  5. class Promise {
  6. constructor(executor) {
  7. this.status = PENDING //默认状态为PENDING
  8. this.value = undefined //存放成功状态的值,默认为undefined
  9. this.reason = undefined //存放失败状态的值,默认为undefined
  10. let resolve = value => {
  11. //调用此方法就是成功
  12. //状态为PENDING时才可以更新状态,防止executor中调用两次resolve/reject方法
  13. if(this.status === PENDING) {
  14. this.status = FULFILLED
  15. this.value = value
  16. }
  17. }
  18. let reject = reason =>{
  19. //调用此方法就是失败
  20. //状态为PENDING时才可以更新状态,防止executor中调用两次resolve/reject方法
  21. if(this.status === PENDING) {
  22. this.status = REJECTED
  23. this.reason = reason
  24. }
  25. }
  26. try {
  27. // 立即执行,将 resolve 和 reject 函数传给使用者
  28. executor(resolve,reject)
  29. } catch (error) {
  30. // 发生异常时执行失败逻辑
  31. reject(error)
  32. }
  33. }
  34. then(onFulfilled,onRejected) {
  35. if(this.status === FULFILLED) {
  36. onFulfilled(this.value)
  37. }
  38. if(this.status === REJECTED) {
  39. onRejected(this.reason)
  40. }
  41. }
  42. }

写完代码测试一下

  1. const promise = new Promise((resolve,reject)=>{
  2. resolve('成功')
  3. }).then(data=>{
  4. console.log('success',data);
  5. },err=>{
  6. console.log('faild',err);
  7. })

控制台输出:
'success 成功'

现在我们已经实现了一个基础版的Promise,但是还不要高兴太早,这里我们只处理了同步操作的Promise。如果在executor()中传入一个异步操作的话呢,试一下

  1. const promise = new Promise((resolve,reject)=>{
  2. setTimeout(()=>{
  3. console.log('成功')
  4. },1000)
  5. }).then(data=>{
  6. console.log('success',data);
  7. },err=>{
  8. console.log('faild',err);
  9. })

执行测试代码后发现,promise没有返回

因为promise调用then方法时,当前的promise并没有成功,一直处于pending状态。所以如果当调用then方法时,当前状态是pending,我们需要先将成功和失败的回调分别存放起来,在executor()的异步任务被执行时,出发resolve或reject,一次嗲用失败或成功的回调

结合思路,优化代码

  1. const PENDING = 'PENDING'
  2. const FULFILLED = 'FULFILLED'
  3. const REJECTED = 'REJECTED'
  4. class Promise {
  5. constructor(executor) {
  6. this.status = PENDING
  7. this.value = undefined
  8. this.reason = undefined
  9. this.onResolvedCallbacks = [] // 存放成功的回调
  10. this.onRejectedCallbacks = [] // 存放失败的回调
  11. let resolve = value =>{
  12. this.status = FULFILLED
  13. this.value = value
  14. //依次将对应的函数执行
  15. this.onResolvedCallbacks.forEach(fn=>fn())
  16. }
  17. let reject = reason =>{
  18. this.status = REJECTED
  19. this.reason = reason
  20. //依次将对应的函数执行
  21. this.onRejectedCallbacks.forEach(fn=>fn())
  22. }
  23. try {
  24. executor(resolve,reject)
  25. } catch (error) {
  26. reject(error)
  27. }
  28. }
  29. then(onFulfilled,onRejected) {
  30. if(this.status === FULFILLED) {
  31. onFulfilled(this.value)
  32. }
  33. if(this.status === REJECTED) {
  34. onRejected(this.reason)
  35. }
  36. if(this.status === PENDING) {
  37. // 如果Promise的状态是pending,需要将 onFulfilled 和 onRejected 函数存放起来等待状态确定后,再依次将对应的函数执行
  38. this.onResolvedCallbacks.push(()=>{
  39. onFulfilled(this.value)
  40. })
  41. this.onRejectedCallbacks.push(()=>{
  42. onRejected(this.reason)
  43. })
  44. }
  45. }
  46. }

测试一下:

  1. const promise = new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve('成功');
  4. },1000);
  5. }).then(
  6. (data) => {
  7. console.log('success', data)
  8. },
  9. (err) => {
  10. console.log('faild', err)
  11. }
  12. )

控制台1s后输出:
'success 成功'

3、then的链式调用&值穿透特性

我们都知道,Promise的优势在于可以链式调用。在我们使用Promise的时候,当then函数中return了一个值,不管是什么值,我们都能在下一个then中获取到,这就是所谓的then链式调用。而且当我们不在then中放入参数。例:promise.then().then(),那么其后面的then一九可以得到之前then返回的值,这就是所谓的值的穿透。那具体如何实现的呢?简单思考一下,如果每次调用then的时候,我们都重新创建一个promise对象,并把上一个then的返回结果传给这个新的promise的then方法,不久可以一致then下去了吗,那我们来试着实现一下,这也是手写promise源码的重中之重。

  1. then的参数onFulfilled和onRejected 可以缺省,如果onFulfilled或者onRejected不是函数,将其忽略,且一九可以在下面的then中获取到之前返回的值
  2. promise可以then多次,每次执行完promise.then方法后返回的都是一个新的promise
  3. 如果then的返回值x是一个普通值,那么就会把这个结果作为参数,传递给下一个then的成功回调中
  4. 如果then抛出了异常,,那么就会把这个异常作为阐述,传递给下一个then的失败的回调中
  5. 如果then的返回值x是一个promise,那么就会等这个promise执行完,promise如果成功,就走下一个then的成功,如果失败,就走下一个then的失败,如果抛出异常,就走下一个then的失败
  6. 如果then的返回值X和promise是同一个引用对象,造成循环引用,则抛出异常,把异常传递给下一个then失败的回调中
  7. 如果then的返回值X是一个promise。且X同时调用resolve函数和reject函数,则第一次调用优先,其他函数调用被忽略

我们将代码补充完整:

  1. const PENDING = 'PENDING';
  2. const FULFILLED = 'FULFILLED';
  3. const REJECTED = 'REJECTED';
  4. const resolvePromise = (promise2, x, resolve, reject) => {
  5. // 自己等待自己完成是错误的实现,用一个类型错误,结束掉 promise Promise/A+ 2.3.1
  6. if (promise2 === x) {
  7. return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  8. }
  9. // Promise/A+ 2.3.3.3.3 只能调用一次
  10. let called;
  11. // 后续的条件要严格判断 保证代码能和别的库一起使用
  12. if ((typeof x === 'object' && x != null) || typeof x === 'function') {
  13. try {
  14. // 为了判断 resolve 过的就不用再 reject 了(比如 reject 和 resolve 同时调用的时候) Promise/A+ 2.3.3.1
  15. let then = x.then;
  16. if (typeof then === 'function') {
  17. // 不要写成 x.then,直接 then.call 就可以了 因为 x.then 会再次取值,Object.defineProperty Promise/A+ 2.3.3.3
  18. then.call(x, y => { // 根据 promise 的状态决定是成功还是失败
  19. if (called) return;
  20. called = true;
  21. // 递归解析的过程(因为可能 promise 中还有 promise) Promise/A+ 2.3.3.3.1
  22. resolvePromise(promise2, y, resolve, reject);
  23. }, r => {
  24. // 只要失败就失败 Promise/A+ 2.3.3.3.2
  25. if (called) return;
  26. called = true;
  27. reject(r);
  28. });
  29. } else {
  30. // 如果 x.then 是个普通值就直接返回 resolve 作为结果 Promise/A+ 2.3.3.4
  31. resolve(x);
  32. }
  33. } catch (e) {
  34. // Promise/A+ 2.3.3.2
  35. if (called) return;
  36. called = true;
  37. reject(e)
  38. }
  39. } else {
  40. // 如果 x 是个普通值就直接返回 resolve 作为结果 Promise/A+ 2.3.4
  41. resolve(x)
  42. }
  43. }
  1. class Promise {
  2. constructor(executor) {
  3. this.status = PENDING;
  4. this.value = undefined;
  5. this.reason = undefined;
  6. this.onResolvedCallbacks = [];
  7. this.onRejectedCallbacks= [];
  8. let resolve = (value) => {
  9. if(this.status === PENDING) {
  10. this.status = FULFILLED;
  11. this.value = value;
  12. this.onResolvedCallbacks.forEach(fn=>fn());
  13. }
  14. }
  15. let reject = (reason) => {
  16. if(this.status === PENDING) {
  17. this.status = REJECTED;
  18. this.reason = reason;
  19. this.onRejectedCallbacks.forEach(fn=>fn());
  20. }
  21. }
  22. try {
  23. executor(resolve,reject)
  24. } catch (error) {
  25. reject(error)
  26. }
  27. }
  28. then(onFulfilled, onRejected) {
  29. //解决 onFufilled,onRejected 没有传值的问题
  30. //Promise/A+ 2.2.1 / Promise/A+ 2.2.5 / Promise/A+ 2.2.7.3 / Promise/A+ 2.2.7.4
  31. onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
  32. //因为错误的值要让后面访问到,所以这里也要跑出个错误,不然会在之后 then 的 resolve 中捕获
  33. onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
  34. // 每次调用 then 都返回一个新的 promise Promise/A+ 2.2.7
  35. let promise2 = new Promise((resolve, reject) => {
  36. if (this.status === FULFILLED) {
  37. //Promise/A+ 2.2.2
  38. //Promise/A+ 2.2.4 --- setTimeout
  39. setTimeout(() => {
  40. try {
  41. //Promise/A+ 2.2.7.1
  42. let x = onFulfilled(this.value);
  43. // x可能是一个proimise
  44. resolvePromise(promise2, x, resolve, reject);
  45. } catch (e) {
  46. //Promise/A+ 2.2.7.2
  47. reject(e)
  48. }
  49. }, 0);
  50. }
  51. if (this.status === REJECTED) {
  52. //Promise/A+ 2.2.3
  53. setTimeout(() => {
  54. try {
  55. let x = onRejected(this.reason);
  56. resolvePromise(promise2, x, resolve, reject);
  57. } catch (e) {
  58. reject(e)
  59. }
  60. }, 0);
  61. }
  62. if (this.status === PENDING) {
  63. this.onResolvedCallbacks.push(() => {
  64. setTimeout(() => {
  65. try {
  66. let x = onFulfilled(this.value);
  67. resolvePromise(promise2, x, resolve, reject);
  68. } catch (e) {
  69. reject(e)
  70. }
  71. }, 0);
  72. });
  73. this.onRejectedCallbacks.push(()=> {
  74. setTimeout(() => {
  75. try {
  76. let x = onRejected(this.reason);
  77. resolvePromise(promise2, x, resolve, reject)
  78. } catch (e) {
  79. reject(e)
  80. }
  81. }, 0);
  82. });
  83. }
  84. });
  85. return promise2;
  86. }
  87. }

测试一下:

  1. const promise = new Promise((resolve,reject)=>{
  2. reject('失败');
  3. }).then().then().then(data => {
  4. console.log(data)
  5. },err=>{
  6. console.log('err',err)
  7. })

控制台输出:
"失败 err"