手撕Promise

手撕Promise

准备

  • 根据Promises/A+ 规范手写Promise
  • 为了方便理解和实验, 每个代码块都是可以单独跑的. 当然相对的整个文档也会看起来比较臃肿.
  • 为了方便识别每个代码块之间的修改与添加, 有被修改与添加的代码我都写上了注释.

开始

new 一个Promise, 传入一个函数, 这个函数立即执行.

class XPromise {
  constructor(executor) {
    executor()
  }
}

const promise = new XPromise((resolve, reject) => {
  console.log('hello world');
})

new完返回的实例promise会有then和catch两个方法, 两个方法都可以传递一个函数, 传递的函数可以在new Promise时用resolve与reject调用.

两个函数是使用的then方法传进来的, 或者catch, 我们需要将这两个函数储存起来.

在PromiseA+规范中是没有catch函数的, 所以我们先实现then方法的行为.

class XPromise {
  constructor(executor) {
    this.onFulfilled = null;
    this.onRejected = null;

    // 添加一个定时器是为了让executor在then后面执行,需要先等then传入回调函数我们在这里才能调用它
    // 后面会优化
    setTimeout(() => {
      executor(this.onFulfilled, this.onRejected)
    }, 0);
  }

  then(fn1, fn2) {
    this.onFulfilled = fn1;
    this.onRejected = fn2;
  }
}

// 1.new Promise
const promise = new XPromise((resolve, reject) => {
  console.log('hello world');
  resolve('resolve action');
})

// 2.给Promise传入成功的回调和失败的回调
promise.then((res) => {
  console.log(res)
}, (err) => {
  console.log(err);
})

实现基本结构我们开始处理Promise的状态 -

Promise的状态一旦确定将不能改变

// 使用常量来描述Promise状态
const PROMISE_STATUS_PENDDING = 'pendding';
const PROMISE_STATUS_FULFILED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';

class XPromise {
  constructor(executor) {
    // 默认状态为pendding
    this.status = PROMISE_STATUS_PENDDING;
    this.onFulfilledFn = null;
    this.onRejectedFn = null;

    setTimeout(() => {
      executor(this.onFulfilled, this.onRejected)
    }, 0);
}

为了可以验证Promise的状态, 而控制resolve与reject函数的执行, 我们需要将用then传进来的函数重新用一个函数包裹

const PROMISE_STATUS_PENDDING = 'pendding';
const PROMISE_STATUS_FULFILED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';

class XPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDDING;
    this.onFulfilled = null;
    this.onRejected = null;

    // 将onFulfilledFn用resolve函数包裹(这个是一个对调函数,供new Promise时传入的回调函数使用)
    const resolve = (value) => {
      // 如果状态已经改变不再执行这个函数
      if (this.status !== PROMISE_STATUS_PENDDING) return;
      // 否则执行
      this.status = PROMISE_STATUS_FULFILED; // 并且改变promise状态为Fullfiled,下面同.
      this.onFulfilled(value);
    }

    // 将onRejectedFn用reject函数包裹
    const reject = (reason) => {
      if (this.status !== PROMISE_STATUS_PENDDING) return;
      this.status = PROMISE_STATUS_REJECTED;
      this.onRejected(reason);
    }
	
    // 将resolve与reject交给 new Promise 时使用
    setTimeout(() => {
      executor(resolve, reject)
    }, 0);
  }

  then(fn1, fn2) {
    this.onFulfilled = fn1;
    this.onRejected = fn2;
  }
}

const promise = new XPromise((resolve, reject) => {
  console.log('hello world');
  resolve('resolve action'); 
  reject('reject action'); // 实现了只会执行一个
})

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

目前, 基本的Promise结构我们就已经做好了. 现在我们开始处理边缘情况

实现每个promise实例可以被独立调用多次

每个promise实例可以被调用任意次, 且每次都是独立的. 意味着每此给promise用then传入的回调都会执行,且不会被覆盖(独立). 我们自己的Promise目前是做不到的.

// 自己的XPromise, 问题复现
const promise = new XPromise((resolve, reject) => {
  console.log('hello world');
  resolve('resolve action'); 
  reject('reject action'); 
})

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

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

// 输出
// hello world
// res2: resolve action
// 可以看到第二次调用会将上一次传入的回调函数给覆盖
// 而原生的Promise多次调用then方法传入函数,所有函数都会执行

实现一个promise可以被多次调用

const PROMISE_STATUS_PENDDING = 'pendding';
const PROMISE_STATUS_FULFILED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';

class XPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDDING;
    // 1.将用then传进来的函数都用一个数组保存
    this.onFulfilledFns = [];
    this.onRejectedFns = [];

    const resolve = (value) => {
      if (this.status !== PROMISE_STATUS_PENDDING) return;
      this.status = PROMISE_STATUS_FULFILED;
      // 3.从此调用是拿到装着回调函数的数组进行遍历调用
      this.onFulfilledFns.forEach(fn => {
        fn(value);
      });
    }

    const reject = (reason) => {
      if (this.status !== PROMISE_STATUS_PENDDING) return;
      this.status = PROMISE_STATUS_REJECTED;
      // 从此调用是拿到装着回调函数的数组进行遍历调用
      this.onRejectedFns.forEach(fn => {
        fn(reason);
      });
    }

    setTimeout(() => {
      executor(resolve, reject)
    }, 0);
  }

  then(fn1, fn2) {
    // 2.将用then传入的函数用一个数组保存起来后,每次调用then传入的函数都储存到数组当中.
    this.onFulfilledFns.push(fn1);
    this.onRejectedFns.push(fn2);
  }
}

const promise = new XPromise((resolve, reject) => {
  console.log('hello world');
  resolve('resolve action');
  reject('reject action');
})

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

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

// 再次打印
// hello world
// res1: resolve action
// res2: resolve action
// 达到目的

增加判断解决异步传入的函数

原生的Promise用定时器加入的函数也会被执行并且返回正确的结果。 而我们的却不会, 因为在定时结束之前我们装回调函数的数组已经被循环执行完毕, 异步添加的将不会执行.

// 原生Promise
const promise = new Promise((resolve, reject) => {
    resolve(123)
})

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

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

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

// hello world
// res1: resolve action
// res2: resolve action
// res3:resolve action
// 自己的
promise.then((res) => {
  console.log('res1:', res)
}, (err) => {
  console.log('err1', err);
})

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

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

// hello world
// res1: resolve action
// res2: resolve action
// 没有第三个函数的执行

解决.

const PROMISE_STATUS_PENDDING = 'pendding';
const PROMISE_STATUS_FULFILED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';

class XPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDDING;
    this.onFulfilledFns = [];
    this.onRejectedFns = [];
    this.value = null;
    this.reason = null;

    const resolve = (value) => {
      // 1.将value与reason储存起来, 供通过异步的方式使用then传入的函数使用.
      this.value = value;
      if (this.status !== PROMISE_STATUS_PENDDING) return;
      this.status = PROMISE_STATUS_FULFILED;
      this.onFulfilledFns.forEach(fn => {
        fn(value);
      });
    }

    const reject = (reason) => {
      this.reason = reason;
      if (this.status !== PROMISE_STATUS_PENDDING) return;
      this.status = PROMISE_STATUS_REJECTED;
      this.onRejectedFns.forEach(fn => {
        fn(reason);
      });
    }
	
    setTimeout(() => {
      executor(resolve, reject)
    }, 0);
  }

  then(fn1, fn2) {
    // 2.如果在往数组中添加函数之前状态已经改变为fullfilled or rejected, 那么不用再往数组里面推, 拿过来直接执行。
    if (this.status === PROMISE_STATUS_FULFILED) {
      fn1(this.value)
    } else if (this.status === PROMISE_STATUS_REJECTED) {
      fn2(this.reason)
    } else {
      this.onFulfilledFns.push(fn1);
      this.onRejectedFns.push(fn2);
    }
  }
}

const promise = new XPromise((resolve, reject) => {
  console.log('hello world');
  resolve('resolve action');
  reject('reject action');
})

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

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

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

// hello world
// res1: resolve action
// res2: resolve action
// res3: resolve action // 1000ms后打印
// 成功实现. 这里如果不明白,代表还不了解目前整个Promise执行每个回调的顺序与时机.

实现Promise的then传入的回调函数加入微任务队列

在PromiseA+中, then传入的回调函数会被加入到微任务队列

constructor(executor) {
    this.status = PROMISE_STATUS_PENDDING;
    this.onFullfilledFns = [];
    this.onRejectedFns = [];
    this.value = null;
    this.reason = null;

    const resolve = (value) => {
      this.value = value;
      if (this.status !== PROMISE_STATUS_PENDDING) return;
      // 如果是pendding状态,立刻将Promise状态变为fulfilled,防止添加多个微任务.
      this.status = PROMISE_STATUS_FULFILED;
      // 这里使用queueMicrotask函数模拟实现将Promise的then传入的回调函数添加到微任务.
      queueMicrotask(() => {
        this.onFullfilledFns.forEach(fn => {
          fn(value);
        });
      })
    }

    const reject = (reason) => {
      this.reason = reason;
      if (this.status !== PROMISE_STATUS_PENDDING) return;
      this.status = PROMISE_STATUS_REJECTED;
      queueMicrotask(() => {
        this.onRejectedFns.forEach(fn => {
          fn(reason);
        });
      })
    }

	// 这里我们就不必要再加定时器
    // 传出去的两个函数是给 new Promise时使用的, 在执行下面这个函数时,resolve与reject两个函数已经存在, 并且推送了一个微任务, 是遍历执行一个装满函数的数组.由于微任务会在同步任务之后执行,后面的then函数正是同步任务.
    executor(resolve, reject)
  }

实现链式调用

// 原生Promise的链式调用
const promise = new Promise((resolve, reject) => {
    resolve(123)
})

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

// 打印
// 123
// abc

自己的then函数根本就没写return, 不必试.

我们现在来实现promise的链式调用( then函数return的细节看下面补充的第二条 )

// 为了能够链式调用, 我们直接给每个then函数返回一个Promise,把之前的then全部用Promise返回 
// 我们的Promise也就是会直接执行的.
then(fn1, fn2) {
    // 直接将其包裹一个Promise并将其返回
    return new XPromise((resolve, reject) => {
        
      if (this.status === PROMISE_STATUS_FULFILED) {
        fn1(this.value)
      } else if (this.status === PROMISE_STATUS_REJECTED) {
        fn2(this.reason)
      } else {
        this.onFulfilledFns.push(fn1);
        this.onRejectedFns.push(fn2);
      }
        
    })
  }
// 给每个回调函数加上try catch来辨别是调用resolve还是调用reject.
return new XPromise((resolve, reject) => {
      if (this.status === PROMISE_STATUS_FULFILED) {
        // 捕捉其错误, 如果执行函数报错就调用reject
        try {
          // 用resolve将上一个函数(fn1)的执行结果返回
          resolve(fn1(this.value))
        } catch (error) {
          // 如果执行上一个(fn1)函数报错了那么就调用reject返回错误信息
          reject(error)
        }
      } else if (this.status === PROMISE_STATUS_REJECTED) {
        try {
          resolve(fn2(this.reason))
        } catch (error) {
          reject(error)
        }
      } else {
        this.onFulfilledFns.push(fn1);
        this.onRejectedFns.push(fn2);
      }
    })
then(fn1, fn2) {
    // fn1是Promise在fullfilled时调用的函数, f2是Promise在rejected时调用的函数
    // 为了能够链式调用, 我们直接给每个then函数返回一个Promise,把之前的then全部用Promise返回 
    // 我们的Promise也就是会直接执行的.
    return new XPromise((resolve, reject) => {
      if (this.status === PROMISE_STATUS_FULFILED) {
        // 捕捉其错误, 如果执行函数报错就调用reject
        try {
          // 用resolve将上一个函数(fn1)的执行结果返回
          resolve(fn1(this.value))
        } catch (error) {
          // 如果执行上一个(fn1)函数报错了那么就调用reject返回错误信息
          reject(error)
        }
      } else if (this.status === PROMISE_STATUS_REJECTED) {
         // 执行失败的回调函数return的结果也是用resolve传递出来,除非这个函数报错.
        try {
          resolve(fn2(this.reason))
        } catch (error) {
          reject(error)
        }
      } else {
        // onFulfilledFns是为了 一个promise实例被用then调用多次 需要执行多个then传入的函数 而建立的储存函数的数组
        // 为了方便拿到每个函数的返回值, 我们将推入数组中的函数再用一个函数包裹.利用了js的闭包.
        this.onFulfilledFns.push(() => {
          try {
            resolve(fn1(this.reason))
          } catch (error) {
            reject(error)
          }
        });
        this.onRejectedFns.push(() => {
          try {
            resolve(fn2(this.reason))
          } catch (error) {
            reject(error)
          }
        });
      }
    })
  }

还有一种情况是在new Promise传入的函数就已经报错的情况, 这个时候我们也应该调用reject


    // 尝试
      try {
        executor(resolve, reject)
    // 捕获到错误
      } catch (error) {
        reject(error)
      }

测试XPromise

const promise = new XPromise((resolve, reject) => {
  console.log('hello world');
  resolve('resolve action');
  reject('reject action');
})

// 1. 第一个then的函数没有写return
promise
  .then((res) => {
    console.log('res1:', res)
  }, (err) => {
    console.log('err1', err);
  })
  .then((res) => {
    console.log('res2:', res)
  }, (err) => {
    console.log('err2', err);
  })

	// hello world
	// res1: resolve action
	// res2: undefined

// 2.第一个then有return一个普通的值
promise
  .then((res) => {
    console.log('res1:', res)
    // return 
    return 123;
  }, (err) => {
    console.log('err1', err);
  })
  .then((res) => {
    console.log('res2:', res)
  }, (err) => {
    console.log('err2', err);
  })

	// hello world
	// res1: resolve action
	// res2: 123

// 3.第一个then的成功时的回调报错(抛出错误)
promise
  .then((res) => {
    console.log('res1:', res)
    // 抛出错误
    throw new Error('啊哦! 出错了~')
  }, (err) => {
    console.log('err1', err);
  })
  .then((res) => {
    console.log('res2:', res)
  }, (err) => {
    // 捕获到错误
    console.log('err2', err);
  })
	// 成功捕获到错误
	// PS F:\Web\demo> node .\XPromise.js
         hello world
         res1: resolve action
         err2 Error: 啊哦! 出错了~
    	 at F:\Web\demo\XPromise.js:91:11
    	 at F:\Web\demo\XPromise.js:49:19
    	 at F:\Web\demo\XPromise.js:33:9 
    	 at internal/process/task_queues.js:141:7
    	 at AsyncResource.runInAsyncScope (async_hooks.js:197:9)
    	 at AsyncResource.runMicrotask (internal/process/task_queues.js:138:8) 

// 4.第一个then中的 失败时的回调函数 return一个普通的值
const promise = new XPromise((resolve, reject) => {
  console.log('hello world');
  reject('reject action');
})

promise
  .then((res) => {
    console.log('res1:', res)
  }, (err) => {
    console.log('err1', err);
    return 123
  })
  .then((res) => {
    console.log('res2:', res)
  }, (err) => {
    console.log('err2', err);
  })

// hello world
// err1 reject action
// res2: 123
// 执行第二个then函数的成功的回调函数

// 5.new Promise传入的回调函数就已经报错
const promise = new XPromise((resolve, reject) => {
  console.log('hello world');
  throw new Error('啊哦!出错了!')
  resolve('resolve action');
  reject('reject action');
})

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

// 打印报错函数调用栈
PS F:\demo\手撕Promise> node .\XPromise.js
hello world
Error: 啊哦!出错了!
    at F:\demo\手撕Promise\XPromise.js:91:9
    at new XPromise (F:\demo\手撕Promise\XPromise.js:39:7)
    at Object.<anonymous> (F:\demo\手撕Promise\XPromise.js:89:17)
    at Module._compile (node:internal/modules/cjs/loader:1101:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47

最终形态

const PROMISE_STATUS_PENDDING = 'pendding';
const PROMISE_STATUS_FULFILED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';

class XPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDDING;
    this.onFullfilledFns = [];
    this.onRejectedFns = [];
    this.value = null;
    this.reason = null;

    const resolve = (value) => {
      // 将value与reason储存起来, 供通过异步的方式使用then传入的函数使用.
      this.value = value;
      if (this.status !== PROMISE_STATUS_PENDDING) return;
      this.status = PROMISE_STATUS_FULFILED;
      // 根据PromiseA+规范, 这里顺便实现一下将Promise的then回调函数添加到微任务.
      queueMicrotask(() => {
        this.onFullfilledFns.forEach(fn => {
          fn(value);
        });
      })
    }

    const reject = (reason) => {
      this.reason = reason;
      if (this.status !== PROMISE_STATUS_PENDDING) return;
      this.status = PROMISE_STATUS_REJECTED;
      queueMicrotask(() => {
        this.onRejectedFns.forEach(fn => {
          fn(reason);
        });
      })
    }


    try {
      executor(resolve, reject)
      // 捕获到错误
    } catch (error) {
      reject(error)
    }
  }

  then(fn1, fn2) {
    // fn1是Promise在fullfilled时调用的函数, f2是Promise在rejected时调用的函数
    // 为了能够链式调用, 我们直接给每个then函数返回一个Promise,把之前的then全部用Promise返回 
    // 我们的Promise也就是会直接执行的.
    return new XPromise((resolve, reject) => {
      if (this.status === PROMISE_STATUS_FULFILED) {
        // 捕捉其错误, 如果执行函数报错就调用reject
        try {
          // 用resolve将上一个函数(fn1)的执行结果返回
          resolve(fn1(this.value))
        } catch (error) {
          // 如果执行上一个(fn1)函数报错了那么就调用reject返回错误信息
          reject(error)
        }
      } else if (this.status === PROMISE_STATUS_REJECTED) {
        // 执行失败的回调函数return的结果也是用resolve传递出来,除非这个函数报错.
        try {
          resolve(fn2(this.reason))
        } catch (error) {
          reject(error)
        }
      } else {
        // onFullfilledFns是为了 一个promise实例被用then调用多次 需要执行多个then传入的函数 		而建立的储存函数的数组
        // 为了方便拿到每个函数的返回值, 我们将推入数组中的函数再用一个函数包裹.利用了js的闭包.
        this.onFullfilledFns.push(() => {
          try {
            resolve(fn1(this.reason))
          } catch (error) {
            reject(error)
          }
        });
        this.onRejectedFns.push(() => {
          try {
            resolve(fn2(this.reason))
          } catch (error) {
            reject(error)
          }
        });
      }
    })
  }
}

暂未实现

  • return Promise时这个promise的状态没有让return出去的Promise决定
  • return 实现then方法的对象.

结束

  • 目前实现了Promise的then方法
  • 其他方法会后续更新

补充

resolve细节

  • resolve( new Promise );

resolve出去的是一个新的Promise, 那么当前Promise的状态会由这个resolve出去的Promise的决定

  • resolve( 一个实现了then方法的对象 )
obj {
    then(resolve,reject	) {
        resolve()
    }
}

那么会给这个对象的then方法传入两个参数, 分别是then回调函数, 与reject回调函数.

Promise实例化的对象

  • 同一个实例promise可以被调用多次then方法

  • then传入的回调函数可以有返回值 ( return )

    • 如果return的是一个普通的值
    return 123
    return 'abc'
    return { a: 1 }
    

    那么Promise内部处理时会将return的值包裹一个Promise

    new Promise((resolve, rej) => {
        resolve(123)
    })
    
    • 如果ruturn的就是一个Promise

    那么这个Promise的状态就return出去的Promise决定.

    • 如果return的是一个实现了then函数的对象,

    那么就由then决定这个Promise的状态.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值