链接:https://juejin.cn/post/6910500073314975758

本文主要讲述如何根据 Promises/A+ 规范,一步步手写一个 Promise 的 polyfill,代码中会配上对应的规范解释。

1. 定义需要的常量和工具方法

// 1. 定义表示promsie状态的常量
const PENDING_STATE = "pending";
const FULFILLED_STATE = "fulfilled";
const REJECTED_STATE = "rejected";
// 2. 定义可复用的工具方法
const isFunction = function(fun) {return typeof fun === "function";
};
const isObject = function(value) {return value && typeof value === "object";
};
复制代码

2. Promsie 构造函数

Promsie 构造函数内部,主要做 5 件事儿:

function Promise(fun) {// 1. 基本的判断: 判断 Promsie 构造函数是否是通过 new 调用,以及调用时传入的参数 fn 是否是一个函数;// 2. 定义 promise 实例的基本属性;// 3. 定义 resolve 方法;// 4. 定义 reject 方法;// 5. 执行 fun 函数;
}
复制代码

2.1 基本的判断

function Promise(fun) {// 1. 基本的判断// 1.1 判断是否是通过new调用if (!this || this.constructor !== Promise) {throw new TypeError("Promise must be called with new");}// 1.2 判断参数fun是否是一个函数if (!isFunction(fun)) {throw new TypeError("Promise constructor's argument must be a function");}// ...
}
复制代码

2.2 定义 promise 实例的基本属性

function Promise(fun) {// ...// 2. 定义基本属性this.state = PENDING_STATE; // promise实例的状态this.value = void 0; // promise的决议值// Promises/A+:2.2.6 一个promise实例,可能会调用多次then函数,所以需要一个数组保存then中注册的回调并记录其调用顺序this.onFulfilledCallbacks = []; // 保存完成回调this.onRejectedCallbacks = []; // 保存拒绝回调// ...
}
复制代码

2.3 定义 resolve 方法

function Promise(fun) {// ...// 3. 定义resolve方法const resolve = (value) => {resolutionProcedure(this, value);};// 主要执行Promise的决议逻辑const resolutionProcedure = function(promise, x) {// ...};// ...
}
复制代码

2.3.1 Promise 的决议逻辑

Promise 的决议逻辑是 Promise 的一大重点,也是一大难点,把这个逻辑搞清楚了,手写 Promise 就成功一半了。Promise 的决议函数 resolutionProcedure 接收 2 个参数,第一个参数是需要决议的promise实例,第二个参数是决议值,即调用resolve(x)的时候传进去的参数x。Promise 的决议程序主要做了 4 件事:

  1. 判断 x 和 promise 是否指向同一个对象;

  2. 判断 x 是否是一个 promise 实例;

  3. 判断是否是 thenable;

  4. x 为其他 js 基础值,且未决议,则直接决议;

function Promise(fun) {// ...const resolutionProcedure = function(promise, x) {// 3.1 判断 x 和 promise 是否指向同一个对象// Promises/A+:2.3.1 如果promise和x引用相同的对象,则抛出一个TypeError为原因拒绝promise。if (x === promise) {return reject(new TypeError("Promise can not resolved with it seft"));}// 3.2 判断x是否是promise// Promises/A+:2.3.2 如果x是一个promise,则直接采用它的决议值进行决议if (x instanceof Promise) {return x.then(resolve, reject);}// 3.3 判断是否是thenable// Promises/A+:2.3.3 如果x是一个对象或函数:if (isObject(x) || isFunction(x)) {let called = false;try {/*** Promises/A+:* 2.3.3.1 Let then be x.then;* 2.3.3.2 如果检索属性x.then导致抛出异常error,则以error为原因拒绝promise;*/// 这里要注意:在规范中有规定检索属性x.then导致抛出异常error的情况处理,以及// 在插件promises-aplus-tests的用例中,也有检索属性x.then的时候直接抛出异常的情况,// 所以,这里的检索then属性,必须写在try的内部,才能捕获异常。let then = x.then;if (isFunction(then)) {/*** Promises/A+:* 2.3.3.3 如果then是一个函数,则用x调用它;第一个参数是 resolvePromise,第二个参数是 rejectPromise;* 2.3.3.3.3 如果同时调用 resolvePromise 和 rejectPromise,或者多次调用同一个参数,则第一个调用具有优先权,后续的调用将被忽略。(所以需要使用 called 进行控制)*/then.call(x,(y) => {if (called) {return;}called = true;// Promises/A+:2.3.3.3.1 如果使用一个值y调用了resolvePromise,则执行[[Resolve]](promise, y),即我们写的 resolutionProcedure(promise, y);resolutionProcedure(promise, y);},(error) => {if (called) {return;}called = true;// Promises/A+:2.3.3.3.2 如果使用一个reason调用了rejectPromise,则以这个reason直接拒绝promise;reject(error);});return;}} catch (error) {/*** Promises/A+:* 2.3.3.3.4 如果调用then函数抛出一个异常:* 2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 被调用,则忽略它。* 2.3.3.3.4.2 否则,以error为理由拒绝promise。*/if (called) {return;}called = true;reject(error);}}// 3.4 x为其他js基础值,且未决议,则直接决议/*** Promises/A+:* 2.3.3.4 如果then不是一个函数,则用x完成promise;* 2.3.4 如果x不是对象或函数,则用x完成promise;* 2.1 Promise的决议状态是不能变的,一旦决议了,就不能再进行决议,所以这里要先判断promise是否已经决议*/if (promise.state === PENDING_STATE) {promise.state = FULFILLED_STATE;promise.value = x;/*** Promises/A+:* 2.2.2.3 onFulfilled函数不允许执行超过一次,即最多只能执行一次*   (决议之后,立即执行保存的回调。因为promise只能决议一次,所以,保存的回调也正好只能执行一次)* 2.2.6.1 所有的onFulfilled回调,必须按照注册的顺序执行*/promise.onFulfilledCallbacks.forEach((callback) => callback());}};// ...
}
复制代码

2.4 定义 reject 方法

function Promise(fun) {// ...// 4. 定义reject方法(reject方法不会解析接收到的值,接收到啥值就直接拿该值作为拒绝的理由)const reject = (reason) => {if (this.state === PENDING_STATE) {this.state = REJECTED_STATE;this.value = reason;/*** Promises/A+:* 2.2.3.3 onRejected不允许执行超过一次,即最多只能执行一次。*   (决议之后,立即执行保存的回调。因为promise只能决议一次,所以,保存的回调也正好只能执行一次)* 2.2.6.2 所有的onRejected回调,必须按照注册的顺序执行*/this.onRejectedCallbacks.forEach((callback) => callback());}};// ...
}
复制代码

2.5 执行 fun 函数

function Promise(fun) {// ...// 5. 执行fun函数try {fun(resolve, reject);} catch (error) {// 这里需要捕获fun函数执行过程中可能出现的错误;如果fun函数执行出错,则直接拒绝promise。reject(error);}// ...
}
复制代码

至此,Promise 构造函数就算完成了,接下来我们来看 Promise 的另一个重头戏:then

3. Promise.prototype.then

为什么把 then 单独从原型方法中拎出来,主要还是因为他是除了 Promise 决议逻辑之外的另一个重难点,所以想单独讲解。从大的方面来说,then方法中主要做了 2 件事:

  1. 处理 onFulfilled 或者 onRejected 不是函数的情况;

  2. 创建并返回一个新的 promise 实例;

  • 2.1 利用包装函数将 onFulfilled 和 onRejected 添加到事件队列(在此,我们使用setTimeout)

  • 2.2 判断当前的 promise 状态,决定如何处理传入的回到函数:

    • 2.2.1 若为 fulfilled,则执行 onFulfilled;

    • 2.2.2 若为 rejected,则执行 onRejected;

    • 2.2.3 如果 promise 未决议,则将回调保存在 onFulfilledCallbacks 和 onRejectedCallbacks 中,待 promise 决议之后再执行对应回调;

3.1 处理 onFulfilled 或者 onRejected 不是函数的情况

Promise.prototype.then = function(onFulfilled, onRejected) {// 1. 处理onFulfilled或者onRejected不是函数的情况// Promises/A+:2.2.1 onFulfilled 和 onRejected都是可选的,如果他们不是函数,就会被忽略。// Promises/A+:2.2.7.3 如果onFulfilled不是函数,而promise1已经是fulfilled,// 则promise2必须用promise1的决议值进行决议,所以这里需要添加 (value) => value 直接返回promise1的决议值onFulfilled = isFunction(onFulfilled) ? onFulfilled : (value) => value;// Promises/A+:2.2.7.4 如果onRejected不是函数,而promise1已经是rejected,// 则promise2必须用promise1拒绝的reason进行拒绝,所以这里需要添加 throw error;onRejected = isFunction(onRejected)? onRejected: (error) => {throw error;};// ...
};
复制代码

3.2 创建并返回一个新的 promise 实例

Promise.prototype.then = function(onFulfilled, onRejected) {// ...// 2. 创建并返回一个新的 promise 实例;// Promises/A+:2.2.7 then函数必须返回一个promise实例;return new Promise((resolve, reject) => {// 2.1 利用包装函数将 onFulfilled 和 onRejected 添加到事件队列(在此,我们使用setTimeout)let wrapOnFulfilled = () => {setTimeout(() => {try {// Promises/A+:2.2.5 onFulfilled和onRejected都必须作为函数调用(采用默认调用方式,而非call、apply或者属性的方式)let x = onFulfilled(this.value);// Promises/A+:2.2.7.1 如果onFulfilled或onRejected返回一个合法值x,就执行Promise决议过程,而非拒绝resolve(x);} catch (error) {//Promises/A+:2.2.7.2 如果onFulfilled或onRejected抛出一个error,就利用error作为reson执行拒绝操作reject(error);}}, 0);};let wrapOnRejected = () => {setTimeout(() => {try {// Promises/A+:2.2.5 onFulfilled和onRejected都必须作为函数调用(采用默认调用方式,而非call、apply或者属性的方式)let x = onRejected(this.value);// Promises/A+:2.2.7.1 如果onFulfilled或onRejected返回一个合法值x,就执行Promise决议过程,而非拒绝resolve(x);} catch (error) {// Promises/A+:2.2.7.2 如果onFulfilled或onRejected抛出一个error,就利用error作为reson执行拒绝操作reject(error);}}, 0);};// 2.2 判断状态// Promises/A+:2.2.2 和 2.2.3 onFulfilled 和 onRejected 都只能在promise被决议之后执行// 2.2.1 若为fulfilled,则执行onFulfilledif (this.state === FULFILLED_STATE) {wrapOnFulfilled();} else if (this.state === REJECTED_STATE) {// 2.2.2 若为rejected,则执行onRejectedwrapOnRejected();} else {// 2.2.3 如果promise未决议,则将回调保存在onFulfilledCallbacks和onRejectedCallbacks中,待promise决议之后再执行对应回调;this.onFulfilledCallbacks.push(wrapOnFulfilled);this.onRejectedCallbacks.push(wrapOnRejected);}});
};
复制代码

4. 其他原型方法

4.1 Promise.prototype.catch

Promise.prototype.catch = function(callback) {return this.then(null, callback);
};
复制代码

4.2 Promise.prototype.finally

// 无论promise成功或失败,finally方法都会执行接收到的回调函数,并返回一个promise实例:
// 1. 如果回调函数执行出错,将以抛出的错误,拒绝新的promise;
// 2. 否则,新返回的promise会沿用旧promise的决议值进行决议。
Promise.prototype.finally = function(callback) {return this.then((data) => {callback();return data;},(error) => {callback();throw error;});
};
复制代码

5. 静态方法

5.1 Promise.resolve

// 如果Promise.resolve接收到的是一个promise,则会直接返回这个promise;否则,则会进一步执行决议操作。
Promise.resolve = function(value) {return value instanceof Promise? value: new Promise((resolve) => resolve(value));
};
复制代码

5.2 Promise.reject

// Promise.reject无论接收到什么,都会直接以接收到的值作为拒绝理由,而不会像resolve一样进行拆解。
Promise.reject = function(reason) {return new Promise((resolve, reject) => reject(reason));
};
复制代码

5.3 Promise.race

// 需要注意的是,如果Promise.race接收到的是一个空数组([]),则会一直挂起,而不是立即决议。
Promise.race = function(promises) {return new Promise((resolve, reject) => {promises.forEach((promise) => {Promise.resolve(promise).then(resolve, reject);});});
};
复制代码

5.4 Promise.all

Promise.all = function(promises) {return new Promise((resolve, reject) => {// 如果Promise.all接收到的是一个空数组([]),它会立即决议。if (!promises.length) {resolve([]);}let result = [];let resolvedPro = 0;for (let index = 0, length = promises.length; index < length; index++) {Promise.resolve(promises[index]).then((data) => {// 注意,这里要用index赋值,而不是push。因为要保持返回值和接收到的promise的位置一致性。result[index] = data;if (++resolvedPro === length) {resolve(result);}},(error) => {reject(error);});}});
};
复制代码

5.5 Promise.allSettled

// Promise.allSettled 返回一个在所有给定的promise都已经fulfilled或rejected后的promise,
// 并带有一个对象数组,每个对象表示对应的promise结果。
Promise.allSettled = function(promises) {return new Promise((resolve, reject) => {if (!promises.length) {resolve([]);}let result = [];let resolvedPro = 0;for (let index = 0, length = promises.length; index < length; index++) {Promise.resolve(promises[index]).then((data) => {// 注意,这里要用index赋值,而不是push。因为要保持返回值和接收到的promise的位置一致性。result[index] = {status: FULFILLED_STATE,value: data,};if (++resolvedPro === length) {resolve(result);}}).catch((error) => {result[index] = {status: REJECTED_STATE,reason: error,};if (++resolvedPro === length) {resolve(result);}});}});
};
复制代码

至此,手写一个 Promise 就算完成了,此 Promise 完全按照 Promise/A+ 的要求写的,也全部通过了promises-aplus-tests的 872 个测试用例。如果您还发现有什么欠缺的地方,欢迎指正;如果觉得对您有用,也请点个赞哦。代码 github 地址: [github.com/ydiguo/Prom…] Promises/A+ 规范(译本): [juejin.cn/post/691047…]

前端学习笔记????

最近花了点时间把笔记整理到语雀上了,方便同学们阅读:公众号回复笔记或者简历

最后

1.看到这里了就点个在看支持下吧,你的「点赞,在看」是我创作的动力。

2.关注公众号前端壹栈,回复「1」加入前端交流群!「在这里有好多前端开发者,会讨论前端知识,互相学习」!

3.也可添加公众号【前端壹栈】,一起成长

按照 Promise/A+ 手写Promise,通过promises-aplus-tests的全部872个测试用例相关推荐

  1. c0语言 测试用例,按照 Promise/A+ 手写Promise,通过promises-aplus-tests的全部872个测试用例...

    本文主要讲述如何根据 Promises/A+ 规范,一步步手写一个 Promise 的 polyfill,代码中会配上对应的规范解释. 1. 定义需要的常量和工具方法// 1. 定义表示promsie ...

  2. 分析Promise,手写Promise,学习Promise,感受Promise

    整体思路分析: * 1.Promise一共有三种状态,成功fulfilled 失败rejected 等待pending  * pending => fulfilled 等待变成成功  * pen ...

  3. 方法 手写promise_JS探索-手写Promise

    无意间在知乎上刷到Monad这个概念,去了解了一下,前端的Promise就是一种Monad模式,所以试着学习一下手写一个Promise. 本文内容主要参考于 只会用?一起来手写一个合乎规范的Promi ...

  4. 【Promise】自定义 - 手写Promise - Promise.all - Promise(executor)

    手写Promise 1. 整体结构框架 2. Promise(executor) 3. Promise.prototype.then 4. Promise.prototype.catch 5. Pro ...

  5. 手写Promise和all、race等方法,附上原理解析

    手写一个迷你版的Promise JavaScript 中的 Promise 诞生于 ES2015(ES6),是当下前端开发中特别流行的一种异步操作解决方案,简单实现一个迷你版本帮助深入理解 Promi ...

  6. javascript --- 手写Promise、快排、冒泡、单例模式+观察者模式

    手写promise 一种异步的解决方案, 参考 Promise代码基本结构 function Promise(executor){this.state = 'pending';this.value = ...

  7. 一个下课的时间带你手写promise!

    要手写前先看看用法,用法就是我们的需求 //直接调用 let promise=new Promise((resolve,reject)=>{resolve('123') }) promise.t ...

  8. 手写 Promise

    手写 Promise 实现一个简易版 Promise 在完成符合 Promise/A+ 规范的代码之前,我们可以先来实现一个简易版 Promise,因为在面试中,如果你能实现出一个简易版的 Promi ...

  9. 面试必备--手写Promise.all与.race

    最近面试被问到了手写Promise .all 与 Promise.race,奈何没有自己实现过,只能阿巴阿巴 面完之后,冷静下来思考了该如何实现,并把他写了下来(在实现过程中确实收获不少,让我对这两个 ...

最新文章

  1. (十三)事件分发器——event()函数,事件过滤
  2. java简单工厂模式_Java 简单工厂模式
  3. [嵌入式]Bootloader的作用
  4. weblogic下开发web项目时修改java文件不用重启的绿色方法,不用修改weblogic的配置文件、不用jar...
  5. css --- [读书笔记] 浮动(float) 与 清除浮动
  6. 前端学习(3204):类式组件
  7. 【idea基础知识】常用快捷键整理
  8. python随机数调用
  9. Spring Boot Executable jar/war 原理
  10. 南昊网上阅卷系统服务器地址,南昊网上阅卷系统全攻略
  11. 中国物联网激荡20年
  12. instsrv.exe srvany.exe 实现不登陆桌面启动
  13. html鼠标移上去变成箭头,js实现鼠标移动到div上变成箭头
  14. c++实现剧情小游戏:哈利波特
  15. 记录一些遇见的bug——Lombok和Mapstruct的冲突导致,A component required a bean of type ‘com.XXX.controller.converter.
  16. 看看电销外呼系统排行,选择哪家外呼公司靠谱?
  17. 线性插值改变图像尺寸_数码相机的成像原理、显示屏尺寸、像素的介绍
  18. 用Defraggler打造自己的免费自动磁盘整理工具
  19. matlab 已实现极差波动,关于黄金期货论文范文写作 中国黄金期货价格SVR智能预测相关论文写作资料...
  20. 奥比中光(Deeyea)配置树莓派

热门文章

  1. 使用http://itunes.apple.com/lookup?id= 获取不到数据
  2. 计算几何(二维)-基本组件(1)
  3. Ubuntu学习系列——添加源
  4. Borg Maze (最小生成树+bfs)
  5. Realtek PCIe GBE Family Controller网卡抓带Vlan Tag的包
  6. 漫步Facebook开源C++库Folly之string类设计
  7. 谈大学英语六级考试语法与改错的复习
  8. jdbc:oracle:thin:@localhost:1521:orcl和jdbc:oracle:thin:@localhost:1521/orcl的区别
  9. uniapp -- 本地数据存储
  10. text-align中justify属性的使用方法