Promise 分析与实现(手撕通过A+规范case)
创始人
2024-05-30 19:34:02
0

文章目录

  • Promise 分析与实现
    • 一、参考资料
    • 二、术语
    • 三、Promise States
      • 1.pending
      • 2.fulfilled
      • 3.rejected
    • 四、Then 方法
      • 1.参数要求
      • 2.onFulfilled 特性
      • 3.onRejected 特性
      • 4.onFulfilled 和 onRejected 应该是微任务
      • 5.then 方法可以被调用多次
      • 6.返回值
      • 7.resolvePromise
    • 五、实现 promise
      • 1.定义并设置三种状态,传入执行器 exec
      • 2.为 exec 添加 reject 和 resolve 方法,执行回调
      • 3.简单实现 then 方法
      • 4.添加 then 方法的对异步的处理
      • 5.完善 then 方法-(链式调用)
      • 6.完善 resolvePromise 的处理
      • 7.添加 resolve、reject、catch 静态方法
      • 8.实现 race、all、allSettled 方法
    • 六、使用 promises-aplus-tests 进行测试
    • 往期精彩文章

Promise 分析与实现

一、参考资料

  • 深入分析 Promise 实现细节
  • Promise A+规范
  • promise v8 源码

二、术语

  • promise是一个有 then 方法的对象或者函数
  • thenable是一个有 then 方法的对象或者函数
  • value是 promise 状态成功时的值,也就是 resolve 的参数, 包括各种数据类型, 也包括 undefined/thenable 或者是 promise
  • reason 是 promise 状态失败时的值, 也就是 reject 的参数, 表示拒绝的原因
  • exception是一个使用 throw 抛出的异常值

三、Promise States

promise 应该有三种状态. 要注意他们之间的流转关系.

1.pending

1.1 初始的状态, 可改变.
1.2 一个 promise 在 resolve 或者 reject 前都处于这个状态。
1.3 可以通过 resolve -> fulfilled 状态;
1.4 可以通过 reject -> rejected 状态;

2.fulfilled

2.1 最终态, 不可变.
2.2 一个 promise 被 resolve 后会变成这个状态.
2.3 必须拥有一个 value 值

3.rejected

3.1 最终态, 不可变.
3.2 一个 promise 被 reject 后会变成这个状态
3.3 必须拥有一个 reason

Tips: 总结一下, 就是 promise 的状态流转是这样的

pending -> resolve(value) -> fulfilled
pending -> reject(reason) -> rejected

四、Then 方法

promise 应该提供一个 then 方法, 用来访问最终的结果, 无论是 value 还是 reason.

promise.then(onFulfilled, onRejected);

1.参数要求

1.1 onFulfilled 必须是函数类型, 如果不是函数, 应该被忽略.
1.2 onRejected 必须是函数类型, 如果不是函数, 应该被忽略.

2.onFulfilled 特性

2.1 在 promise 变成 fulfilled 时,应该调用 onFulfilled, 参数是 value
2.2 在 promise 变成 fulfilled 之前, 不应该被调用.
2.3 只能被调用一次 (所以在实现的时候需要一个变量来限制执行次数)

3.onRejected 特性

3.1 在 promise 变成 rejected 时,应该调用 onRejected, 参数是 reason
3.2 在 promise 变成 rejected 之前, 不应该被调用.
3.3 只能被调用一次(所以在实现的时候需要一个变量来限制执行次数)

4.onFulfilled 和 onRejected 应该是微任务

这里用queueMicrotask来实现微任务的调用.

5.then 方法可以被调用多次

5.1 promise 状态变成 fulfilled 后,所有的 onFulfilled 回调都需要按照then 的顺序执行, 也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个 onFulfilled 的回调)
5.2 promise 状态变成 rejected 后,所有的 onRejected 回调都需要按照then 的顺序执行, 也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个 onRejected 的回调)

6.返回值

  • then 应该返回一个 promise
promise2 = promise1.then(onFulfilled, onRejected);

6.1 onFulfilled 或 onRejected 执行的结果为 x, 调用 resolvePromise
6.2 如果 onFulfilled 或者 onRejected 执行时抛出异常 e, promise2 需要被 reject
6.3 如果 onFulfilled 不是一个函数, promise2 以 promise1 的 value 触发 fulfilled
6.4 如果 onRejected 不是一个函数, promise2 以 promise1 的 reason 触发 rejected

7.resolvePromise

resolvePromise(promise2, x, resolve, reject);
  • 如果 promise2 和 x 相等,那么reject TypeError,防止循环引用,造成死循环
  • 如果 x 是一个 promise
    • 如果 x 是 pending 态,那么 promise 必须要在 pending,直到 x 变成 fulfilled or rejected.
    • 如果 x 被 fulfilled, 用相同的 value 执行 promise
    • 如果 x 被 rejected, 用相同的 reason 拒绝 promise
  • 如果 x是一个 object 或者 是一个 function
    • 首先取出 then,let then = x.then.
    • 如果x.then 这步出错,那么 reject(e)
    • 如果then 是一个函数,then.call(x, resolvePromiseFn, rejectPromiseFn)
    • resolvePromiseFn 的 入参是 y, 执行 resolvePromise(promise2, y, resolve, reject);
    • rejectPromiseFn 的 入参是 r, resolvePromise(promise2, r, resolve, reject)
    • 如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。使用 called 标识
    • 如果调用 then 抛出异常 e
    • 如果 resolvePromise 或 rejectPromise 已经被调用,那么忽略
  • 如果 then 不是一个 function. resolve(x)

五、实现 promise

1.定义并设置三种状态,传入执行器 exec

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';class MyPromise {constructor(exec) {this.status = PENDING;this.value = null;this.reason = null;// 执行器传入后立即执行exec();}
}

2.为 exec 添加 reject 和 resolve 方法,执行回调

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';class MyPromise {constructor(exec) {this.status = PENDING;this.value = null;this.reason = null;// resolve和reject被回调的时候,状态流转// 这里使用箭头函数,绑定resolve和reject的this始终指向MyPromiselet resolve = (value) => {if (this.status === PENDING) {this.status = FULFILLED;this.value = value;}};let reject = (reason) => {if (this.status === PENDING) {this.status = REJECTED;this.reason = reason;}};// 若执行器报错,直接reject出去try {// 执行器传入后立即执行exec(resolve, reject);} catch (error) {reject(error);}}
}

3.简单实现 then 方法

then(onFulfilled, onRejected) {if (this.status === FULFILLED) {// 调用成功回调,并且把值返回onFulfilled(this.value);} else if (this.status === REJECTED) {// 调用失败回调,并且把原因返回onRejected(this.reason);}
}

到这个地方一个简单的 promise 的骨架已经形成了,写一个测试检查一下

const promise = new MyPromise((resolve, reject) => {resolve('success');reject('err');// 如果这里使用异步的方法,上述的then是感觉不到的// setTimeout(() => {//     resolve('success')// })
});promise.then((value) => {console.log('resolve', value);},(reason) => {console.log('reject', reason);},
);// 输出: resolve success

4.添加 then 方法的对异步的处理

思考:为什么使用异步的方法就感受不到了呢?
上面的写法, 是在 then 函数被调用的瞬间就会执行.
那这时候如果 status 还没变成 fulfilled 或者 rejected 怎么办, 很有可能还是 pending 的
那么我们首先要拿到所有的回调, 然后才能在某个时机去执行他.
新建两个数组, 来分别存储成功和失败的回调, 调用 then 的时候, 如果还是 pending 就存入数组

class MyPromise {constructor(exec) {this.status = PENDING;this.value = null;this.reason = null;this.resolveCallbacks = [];this.rejectCallbacks = [];// resolve和reject被回调的时候,状态流转// 这里使用箭头函数,绑定resolve和reject的this始终指向MyPromiselet resolve = (value) => {if (this.status === PENDING) {this.status = FULFILLED;this.value = value;this.resolveCallbacks.forEach((fn) => fn());}};let reject = (reason) => {if (this.status === PENDING) {this.status = REJECTED;this.reason = reason;this.rejectCallbacks.forEach((fn) => fn());}};// 若执行器报错,直接reject出去try {// 执行器传入后立即执行exec(resolve, reject);} catch (error) {reject(error);}}then(onFulfilled, onRejected) {if (this.status === FULFILLED) {// 调用成功回调,并且把值返回onFulfilled(this.value);} else if (this.status === REJECTED) {// 调用失败回调,并且把原因返回onRejected(this.reason);} else if (this.status === PENDING) {this.resolveCallbacks.push(onFulfilled);this.rejectCallbacks.push(onRejected);}}
}

5.完善 then 方法-(链式调用)

  1. onFulfilled 和 onRejected 如果不是函数,就返回原 value 或 reason
  2. then 方法返回一个 promise 对象
    1. onFulfilled 或 onRejected 执行的结果是 x,调用 resolvePromise
    2. 如果 onFulfilled 或者 onRejected 执行时抛出异常 e,promise2 需要被 reject,其 reason 为 e
  3. onFulfilled 和 onRejected 是微任务,需要使用 queueMicrotask 或者 setTimeout 包裹
isFunction(params) {return typeof params === 'function';
}then(onFulfilled, onRejected) {// 1.onFulfilled和onRejected如果不是函数,就返回原value或reasonconst fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : value => value;const rejectedFn = this.isFunction(onRejected) ? onRejected : reason => { throw reason };// 2.then方法返回一个promise对象const p2 = new MyPromise((resolve, reject) => {if (this.status === FULFILLED) {// fulFilledFn(this.value)// 3.3. onFulfilled和onRejected是微任务,需要使用queueMicrotask或者setTimeout包裹queueMicrotask(() => {// 2.2 如果onFulfilled或者onRejected执行时抛出异常e,promise2需要被reject,其reason为etry {// 2.1 onFulfilled或onRejected执行的结果是x,调用resolvePromise// 获取成功回调函数的执行结果const x = fulFilledFn(this.value);// 传入 resolvePromise 集中处理this.resolvePromise(p2, x, resolve, reject);} catch (e) {reject(e);}})} else if (this.status === REJECTED) {// rejectedFn(this.reason);queueMicrotask(() => {try {const x = rejectedFn(this.value);// 传入 resolvePromise 集中处理this.resolvePromise(x, resolve, reject);} catch (e) {reject(e);}})} else if (this.status === PENDING) {this.resolveCallbacks.push(() => {queueMicrotask(() => {try {const x = fulFilledFn(this.value);// 传入 resolvePromise 集中处理this.resolvePromise(x, resolve, reject);} catch (e) {reject(e);}})});this.rejectCallbacks.push(() => {queueMicrotask(() => {try {const x = rejectedFn(this.value);// 传入 resolvePromise 集中处理this.resolvePromise(x, resolve, reject);} catch (e) {reject(e);}})});}});return p2;
}resolvePromise(x, resolve, reject) {// 判断x是不是 MyPromise 实例对象if (x instanceof MyPromise) {// 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected// x.then(value => resolve(value), reason => reject(reason))// 简化之后x.then(resolve, reject);} else {// 普通值resolve(x);}
}

发现上面 promise2 中很多重复的代码,进行抽离

then(onFulfilled, onRejected) {// 1.onFulfilled和onRejected如果不是函数,就返回原value或reasonconst fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : value => value;const rejectedFn = this.isFunction(onRejected) ? onRejected : reason => { throw reason };// 2.then方法返回一个promise对象const p2 = new MyPromise((resolve, reject) => {const fulfilledMicrotask = () => {// 3.3. onFulfilled和onRejected是微任务,需要使用queueMicrotask或者setTimeout包裹queueMicrotask(() => {// 2.2 如果onFulfilled或者onRejected执行时抛出异常e,promise2需要被reject,其reason为etry {// 2.1 onFulfilled或onRejected执行的结果是x,调用resolvePromise// 获取成功回调函数的执行结果const x = fulFilledFn(this.value);// 传入 resolvePromise 集中处理this.resolvePromise(p2, x, resolve, reject);} catch (e) {reject(e);}})}const rejectedMicrotask = () => {queueMicrotask(() => {try {const x = rejectedFn(this.reason);// 传入 resolvePromise 集中处理this.resolvePromise(p2, x, resolve, reject);} catch (e) {reject(e);}})}if (this.status === FULFILLED) {// fulFilledFn(this.value)fulfilledMicrotask();} else if (this.status === REJECTED) {// rejectedFn(this.reason);rejectedMicrotask();} else if (this.status === PENDING) {this.resolveCallbacks.push(fulfilledMicrotask);this.rejectCallbacks.push(rejectedMicrotask);}});return p2;
}

6.完善 resolvePromise 的处理

其实整体的处理逻辑与原来相似,不过是对边界情况更加细化了

resolvePromise(x, resolve, reject) {// 判断x是不是 MyPromise 实例对象if (x instanceof MyPromise) {// 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected// x.then(value => resolve(value), reason => reject(reason))// 简化之后x.then(resolve, reject);} else {// 普通值resolve(x);}
}

resolvePromise(promise2, x, resolve, reject);
  • 防止循环引用,这里要给 resolvePromise 函数签名加一个新参数,判断 promise2 和 x 是否为同一个 promise
  • 如果 x是一个 object 或者 是一个 function
    • 如果 x 是 null,直接 resolve(x)出去
    • 然后取出 then,let then = x.then.
    • 如果x.then 这步出错,那么 reject(e)
    • 如果then 是一个函数,then.call(x, resolvePromiseFn, rejectPromiseFn)
      • resolvePromiseFn 的入参是 y, 如果被调用,执行resolvePromise(promise2, y, resolve, reject);
      • rejectPromiseFn 的入参是 r, 如果被调用,执行 reject®
      • 如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。使用called标识
      • 如果调用 then 抛出异常 e
      • 如果 resolvePromise 或 rejectPromise 已经被调用,那么忽略,if(called) return
    • 如果 then 不是一个 function. resolve(x)
resolvePromise(promise2, x, resolve, reject) {if (promise2 === x) {throw new TypeError('循环引用')}if (typeof x === 'object' || typeof x === 'function') {if (x === null) {return resolve(x);}let then;try {then = x.then;} catch (e) {return reject(e);}if (typeof then === 'function') {let called = false;try {then.call(x, y => {if (called) return;called = true;this.resolvePromise(promise2, y, resolve, reject)}, r => {if (called) return;called = true;reject(r);})} catch (e) {if (called) return;reject(e);}} else {resolve(x);}} else {resolve(x);}
}

7.添加 resolve、reject、catch 静态方法

static resolve(params) {if (params instanceof MyPromise) {return params;}return new MyPromise((resolve, reject) => {resolve(params);})}static reject(reason) {return new MyPromise((resolve, reject) => {reject(reason);})
}catch(onRejected){return this.then(null, onRejected)
}

8.实现 race、all、allSettled 方法

const p = Promise.race([p1, p2, p3]);
该方法是将多个 Promise 实例,包装成一个新的 Promise 实例。
只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变。
那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。

static race(promises) {if (!Array.isArray(promises)) {throw new TypeError('arguments must be an array')}return new MyPromise((resolve, reject) => {if (promises.length === 0) {return resolve();}for (let i = 0; i < promises.length; i++) {MyPromise.resolve(promises[i]).then(resolve, reject);}})
}static all(promises) {return new MyPromise((resolve, reject) => {if (!Array.isArray(promises)) {throw new TypeError('arguments must be an array')}if (promises.length === 0) {return resolve([]);}let result = [];let count = 0;for (let i = 0; i < promises.length; i++) {MyPromise.resolve(promises[i]).then(value => {result[i] = value;count++;if (count === promises.length) {resolve(result);}}, reject);}})
}// 返回所有promise的状态和结果
static allSettled(promises) {return new MyPromise((resolve, reject) => {if (!Array.isArray(promises)) {throw new TypeError('arguments must be an array')}let count = 0;let result = [];for (let i = 0; i < promises.length; i++) {MyPromise.resolve(promises[i]).then(value => {result[i] = {status: 'fulfilled',value}count++;if (count === promises.length) {resolve(result);}}, reason => {result[i] = {status: 'rejected',reason}count++;if (count === promises.length) {resolve(result);}})}})
}

六、使用 promises-aplus-tests 进行测试

// npm i promises-aplus-tests -g
// 命令行执行 => promises-aplus-tests [待测试文件] 即可验证
MyPromise.deferred = function () {var result = {};result.promise = new MyPromise(function (resolve, reject) {result.resolve = resolve;result.reject = reject;});return result;
};
module.exports = MyPromise;

往期精彩文章

  • leetcode-js刷题记录&数据结构
  • docker下YApi部署教程-支持swagger数据导入
  • 带你深入理解什么叫js闭包
  • 使用Object.defineProperty进行数据劫持,实现响应式原理-剖析vue2.0
  • 前端性能优化之rel=“prefetch“预/懒加载功能
  • 前端唤起相机的方法H5+JS

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...