链接: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. 基于C++的PyTorch模型部署
  2. 【Java】“异常”详解
  3. 产品需求管理经验分享
  4. 5G NGC — 关键技术 — R16 eSBA
  5. 使有用计算机不注意卫生,对您有用的与电脑清洁相关的知识
  6. 【模板】可持久化线段树 1(主席树)
  7. 安装MariaDB和Apache
  8. 原生js cookie本地存储
  9. exchanger_如何通过示例在Java中使用Exchanger
  10. charts 画饼图
  11. 退出所有循环_Python学习之路9—循环的总结
  12. (一)golang工作区
  13. call()与apply()的区别与作用
  14. 求教一个关于网站抓取生成地图的问题
  15. 如何让Toast响应点击事件等基础Android基础文章N篇
  16. ffmpeg之让视频快进
  17. 【连载】【STM32神舟III号实验例程】SysTick实验(11)
  18. Lotka–Volterra equation Competitive Lotka–Volterra equations
  19. stm32f103c8t9控制TFT显示屏
  20. Win11怎么在右键菜单添加一键关机选项

热门文章

  1. ios App Technical Support
  2. 受得了多大的委屈,才做得了多大的事
  3. java 秒表_Java的秒表类
  4. C++入门——缓冲区溢出
  5. 2021年G3锅炉水处理免费试题及G3锅炉水处理操作证考试
  6. 第五节:Three.js光源的类型【Three.js整理】
  7. 【element 】使用xlsx、FileSaver实现导出,CDN引入,FileSaver.saveAs is not a function.saveAs is not a function
  8. 11fdsf-02dsf
  9. centos7如何修改IP地址
  10. 浅谈: 计算机—JVM—Java线程—池