手撕promise源码及其原理
2021/11/11 17:10:28
本文主要是介绍手撕promise源码及其原理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
前言
哈哈,好久没写博客了,主要是因为这些日子刚入职了网易,一直在适应工作,还有学校里各种乱七八糟的琐事,所以一直没有来得及写。今天刚好做完了一个迭代,难得有空闲时间来写篇博客(其实是摸鱼~)。
之前我也写过一篇手撕promise,只不过那篇只有代码,没有解释,所以不太容易理解。这篇我准备先从理论说起,再配合代码进行介绍。
如果在看这篇文章之前还没有了解promise的基础知识,请访问JS的Promise对象
哈哈哈哈,就不说废话了,直接进入正题。
一、手撕Promise必须知道的一些概念
1. 高阶函数
高阶函数的概念:
(1)一个函数的参数是一个函数,我们可以成这个函数为高阶函数。
(2)一个函数返回一个函数,我们可以称这个函数为高阶函数。
(3)符合以上任意一点,我们就可以称这个函数为高阶函数。
2. 装饰器模式
概念(摘自菜鸟教程):
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
将高阶函数和装饰器模式结合使用,举个例子:
function say(who) { // 普通的函数 console.log("say", who); } // 对原函数进行扩展 但是不破坏原函数 // @装饰器对类来扩展 Function.prototype.before = function (beforeSay) { // 接受到了回调方法 return (...args) => { // newSay beforeSay(...args); this(...args); }; }; let beforeSay = (args) => { // 传入一个回调方法 console.log("say before", args); }; let newSay = say.before(beforeSay); newSay("我"); // 这里调用的应该是新的方法
这里使用装饰器模式给原来的say方法加了一个新的功能beforeSay
3. 柯里化函数
我之前写过一篇柯里化函数的博客,简单来说,柯里化函数就是利用了闭包的预存储的功能,它将一个多个参数的函数,转化成一个个参数传入的函数。
举个例子:
// 原本fn有四个参数,a,b,c,d function fn(a,b,c,d){...} // 通过柯里化,可以转化成 fn(a)(b)(c)(d)
4. 发布订阅模式
发布订阅模式是前端中一种常见的设计模式vue中大量使用的观察者模式便是基于发布订阅模式衍变而来的。
发布订阅模式有一个发布者、一个订阅者和一个事件池。发布者将消息发布到事件池中,订阅者可以从事件池中订阅消息,事件池有一个on方法和一个emit方法,通过on方法发布消息,通过emit方法执行消息事件。在发布订阅模式中,发布者和订阅者没有强耦合关系。
举个例子(文件在node环境中执行):
const fs = require("fs"); let events = { arr = [], on(){ this.arr.push(fn); } emit(){ this.arr.forEach(fn=>fn()); } } events.on(function(){ console.log('每次读取完毕后都执行') }) events.on(function(){ if(Object.keys(person).length === 2){ console.log('读取完毕') } }) let person= {}; fs.readFile("./a.txt", "utf8", function (err, data) { person.name = data; events.emit(); }); fs.readFile("./b.txt", "utf8", function (err, data) { person.age = data; events.emit(); });
5. 观察者模式
观察者模式中有一个观察者和一个被观察者,被观察者有一个自身状态,当自身状态改变了,通知所有观察者执行update方法触发事件。在观察者模式中,观察者和被观察者是强耦合的。
举个例子:
class Subject { // 被观察者 (需要有一个自身的状态 ,状态变化了要通知所有的观察者) constructor(name){ this.name = name this.observers = [] this.state = '我开心的在玩' } attach(o){ this.observers.push(o); } setState(newState){ this.state = newState; this.observers.forEach(o=>o.update(this)); } } class Observer{ // 观察者 constructor(name){ this.name = name; } update(s){ console.log(this.name+":" + s.name +'当前的状态是'+s.state) } } let s = new Subject('小宝宝') let o1 = new Observer('爸爸'); let o2 = new Observer('妈妈'); // 订阅模式 s.attach(o1) s.attach(o2) s.setState('有人咬我,不开心') s.setState('老师表扬我了 开心了')
6. 手写promise基础版
有了上面的基础,我们进行手写promise就会容易些了
首先根据promise的定义:
- promise是一个类,在使用的时候需要new这个类
- 在newPromise的时候,需要传入一个executor执行器,默认会立即被调用,而且参数有两个:resolve和reject
- promise有三个状态,分别是:pendding 默认等待态 onfulfilled 成功态 onrejected 失败态
我们的promise默认就是pendding,当用户调用resolve时会变成成功态,调用reject的时候会变成失败态
成功可以传入成功的原因,失败可以传入失败的原因 - new Promise 会返回一个promise实例 这个实例上有一个then方法 , then方法中有两个参数一个是成功的回调,一个是失败的回调
- 走向失败有两种情况:reject() 、用户主动抛出异常
- 一个promise中可以then多次 (发布订阅模式)
- promise的状态是不能从成功变成失败,也不能从失败变成成功 只有pending的时候才能更改状态
我们来写一个低配版promise
const PENDING = "PENDING"; const FULFILLED = "FULFILLED"; const REJECTED = "REJECTED"; class Promise { constructor(exector) { this.status = PENDING; this.value = undefined; // 成功的原因 this.reason = undefined; // 失败的原因 this.onResolvedCallbacks = []; // 存放成功的回调 this.onRejectedCallbacks = []; // 存放失败的回调 const resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED; this.value = value; this.onResolvedCallbacks.forEach((fn) => fn()); } }; // 每次new 都生成两个方法 reoslve,reject const reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; this.onRejectedCallbacks.forEach((fn) => fn()); } }; try { exector(resolve, reject); // 传递给用户两个参数 } catch (e) { reject(e); } } then(onFulfilled, onRejected) { if (this.status == FULFILLED) { onFulfilled(this.value); } if (this.status == REJECTED) { onRejected(this.reason); } if (this.status == PENDING) { // 稍后成功了 除了执行回调外 还有其他的逻辑 this.onResolvedCallbacks.push(() => { // todo... onFulfilled(this.value); }); this.onRejectedCallbacks.push(() => { // todo... onRejected(this.reason); }); } } } module.exports = Promise;
7. 手写promise完全版
接下来让我们完善一下我们写的promise,根据promiseA+规范:
- then方法必须返回一个promise
- 如果onFulfilled或onRejected返回一个x,就执行 Promise Resolution procedure 并把x传进去
- 如果onFulfilled不是一个函数并且我们的promise实例已经为满足态,则then返回的promise实例必须为满足态并且和我们的promise实例有相同的值
- 如果onRejected不是一个函数并且我们的promise实例已经为失败态,则then返回的promise实例必须为失败态并且和我们的promise实例有相同的失败原因
- 并且为了执行 Promise Resolution procedure ,我们要为其增加一个promise规范中最核心的一个方法:resolvePromise。
上代码:
const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected"; function resolvePromise(x, promise2, resolve, reject) { // x 决定promise2的状态走成功还是失败 // 所有promise都要遵循这个规范,这样就可以保证不同人写的promise可以混用 // 核心就在这个resolvePromise方法中 } class Promise { constructor(executor) { this.status = PENDING; this.value = undefined; this.reason = undefined; this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; const resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED; this.value = value; this.onFulfilledCallbacks.forEach(fn => fn()); } } const reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; this.onRejectedCallbacks.forEach(fn => fn()); } } try { executor(resolve, reject); } catch (e) { resolve(e); } } then(onFulfilledCallback, onRejectedCallback) { let promise2 = new Promise((resolve, reject) => { if (this.status === FULFILLED) { setTimeout(()=>{ try{ let x = onFulfilledCallback(this.value); resolvePromise(x, promise2, resolve, reject); } catch (e) { reject(e); } }, 0); } if (this.status === REJECTED) { setTimeout(()=>{ try{ let x = onRejectedCallback(this.reason); resolvePromise(x, promise2, resolve, reject); } catch (e) { reject(e); } }, 0); } if (this.status === PENDING) { this.onFulfilledCallbacks.push(() => { setTimeout(()=>{ try { let x = onFulfilledCallback(this.value); resolvePromise(x, promise2, resolve, reject); } catch (e) { reject(e); } }, 0); }); this.onRejectedCallbacks.push(() => { setTimeout(()=>{ try{ let x = onRejectedCallback(this.reason); resolvePromise(x, promise2, resolve, reject); } catch (e) { reject(e); } }, 0); }); return promise2; } }); } } module.exports = Promise;
上述内容完成后,我们自己的promise已经初具雏形了,接下来就让我们来写promise中最核心的一个方法:resolvePromise方法。同样是根据Promise/A+规范:
在这里插入图片描述
- 如果promise和x引用了同一个对象,则返回一个拒绝态的promise,结果为一个TypeError
- 如果x是一个promise
- 如果x状态为pending,promise必须保持pending状态知道x变成fulfilled态或者rejected态
- 当x被满足时(就是变成满足态时),以相同的值来执行promise
- 当x被拒绝时,以相同的原因来执行promise
- 除此之外,如果x是一个对象或者一个函数(这个地方用来判断x是否为一个promise,是用来规范不同的人写的promise的,并且这条规则可以覆盖上三条规则)
- 定义一个then变量,把x.then赋值给它
- 如果检索属性x.then导致抛出异常e,则以e为理由拒绝promise
- 如果then是一个函数,则以x为它的this调用它,第一个参数resolvePromise,第二个参数rejectPromise,其中:
- 如果/当resolvePromise值为y时,执行[[Resolve]](promise, y)
- 如果/当rejectPromise的原因是r,则用r拒绝promise
- 如果同时调用resolvePromise和rejectPromise,或者对同一参数进行多次调用,则第一个调用优先,其他调用将被忽略
- 如果调用then抛出异常e
- 如果resolvePromise或rejectPromise被调用,则忽略它
- 否则,以e为理由拒绝promise
- 如果then不是函数,让x变成满足态
- 如果x不是一个对象或函数,让x变成满足态
上代码:
const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected"; function resolvePromise(x, promise2, resolve, reject) { // x 决定promise2的状态走成功还是失败 if (promise2 === x) { return reject(new TypeError("循环引用")); } // 判断x是不是一个promise先保证x得是一个对象或者函数,如果不是对象或者函数,则x一定不是promise if ((typeof x === "object" && x !== null) || typeof x === "function") { let called; // 我们用called判断下面过程是否执行过了,如果执行过了,就不再执行 // 我们需要看这个x上有没有then方法,有then方法才说明它是一个promise try { let then = x.then; //x可能是别人写的promise,那么取then有风险 if (typeof then === "function") { then.call(x, y => { if (called) return; called = true; resolvePromise(y, promise2, resolve, reject); // 递归解析直到我们的y的值是一个普通值 }, r => { if (called) return; called = true; reject(r); }) } else { // 没有then方法都执行这里 resolve(x); // 这里x只是一个普通对象 } } catch (e) { if (called) return; called = true; reject(e); } } else { // 这里x只是一个普通的值,直接把x传给promise2即可 resolve(x); } // 所有promise都要遵循这个规范,这样就可以保证不同人写的promise可以混用 // 核心就在这个resolvePromise方法中 } class Promise { constructor(executor) { this.status = PENDING; this.value = undefined; this.reason = undefined; this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; const resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED; this.value = value; this.onFulfilledCallbacks.forEach(fn => fn()); } } const reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; this.onRejectedCallbacks.forEach(fn => fn()); } } try { executor(resolve, reject); } catch (e) { reject(e); } } then(onFulfilledCallback, onRejectedCallback) { // 有可能这个onFulfilledCallback,onRejectedCallback是可选的,所以用户没填,我们要自己给它补上(穿透特性) onFulfilledCallback = typeof onFulfilledCallback === "function" ? onFulfilledCallback : function (data) { return data; }; onRejectedCallback = typeof onRejectedCallback === "function" ? onRejectedCallback : err =>{ throw err; }; let promise2 = new Promise((resolve, reject) => { if (this.status === FULFILLED) { setTimeout(() => { try { let x = onFulfilledCallback(this.value); resolvePromise(x, promise2, resolve, reject); } catch (e) { reject(e); } }, 0); } if (this.status === REJECTED) { setTimeout(() => { try { let x = onRejectedCallback(this.reason); resolvePromise(x, promise2, resolve, reject); } catch (e) { reject(e); } }, 0); } if (this.status === PENDING) { this.onFulfilledCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilledCallback(this.value); resolvePromise(x, promise2, resolve, reject); } catch (e) { reject(e); } }, 0); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejectedCallback(this.reason); resolvePromise(x, promise2, resolve, reject); } catch (e) { reject(e); } }, 0); }); } }); return promise2; } } // 安装此模块来测试自己写的promise是否符合规范,并且要加上Promise.deferred // npm install promises-aplus-tests -g // promises-aplus-tests 3.promise // catch Promise.resolve Promise.reject // 在测试的时候 会测试你的promise对象是否符合规范 Promise.deferred = function () { let dfd = {}; dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve; dfd.reject = reject; }) return dfd } module.exports = Promise;
附上一个通过测试的截图
8. 完善promise其他功能
加入catch、all、finally方法
const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected"; function resolvePromise(x, promise2, resolve, reject) { // x 决定promise2的状态走成功还是失败 if (promise2 === x) { return reject(new TypeError("循环引用")); } // 判断x是不是一个promise先保证x得是一个对象或者函数,如果不是对象或者函数,则x一定不是promise if ((typeof x === "object" && x !== null) || typeof x === "function") { let called; // 我们用called判断下面过程是否执行过了,如果执行过了,就不再执行 // 我们需要看这个x上有没有then方法,有then方法才说明它是一个promise try { let then = x.then; //x可能是别人写的promise,那么取then有风险 if (typeof then === "function") { then.call(x, y => { if (called) return; called = true; resolvePromise(y, promise2, resolve, reject); // 递归解析直到我们的y的值是一个普通值 }, r => { if (called) return; called = true; reject(r); }) } else { // 没有then方法都执行这里 resolve(x); // 这里x只是一个普通对象 } } catch (e) { if (called) return; called = true; reject(e); } } else { // 这里x只是一个普通的值,直接把x传给promise2即可 resolve(x); } // 所有promise都要遵循这个规范,这样就可以保证不同人写的promise可以混用 // 核心就在这个resolvePromise方法中 } class Promise { constructor(executor) { this.status = PENDING; this.value = undefined; this.reason = undefined; this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; const resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED; this.value = value; this.onFulfilledCallbacks.forEach(fn => fn()); } } const reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; this.onRejectedCallbacks.forEach(fn => fn()); } } try { executor(resolve, reject); } catch (e) { reject(e); } } then(onFulfilledCallback, onRejectedCallback) { // 有可能这个onFulfilledCallback,onRejectedCallback是可选的,所以用户没填,我们要自己给它补上(穿透特性) onFulfilledCallback = typeof onFulfilledCallback === "function" ? onFulfilledCallback : function (data) { return data; }; onRejectedCallback = typeof onRejectedCallback === "function" ? onRejectedCallback : err => { throw err; }; let promise2 = new Promise((resolve, reject) => { if (this.status === FULFILLED) { setTimeout(() => { try { let x = onFulfilledCallback(this.value); resolvePromise(x, promise2, resolve, reject); } catch (e) { reject(e); } }, 0); } if (this.status === REJECTED) { setTimeout(() => { try { let x = onRejectedCallback(this.reason); resolvePromise(x, promise2, resolve, reject); } catch (e) { reject(e); } }, 0); } if (this.status === PENDING) { this.onFulfilledCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilledCallback(this.value); resolvePromise(x, promise2, resolve, reject); } catch (e) { reject(e); } }, 0); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejectedCallback(this.reason); resolvePromise(x, promise2, resolve, reject); } catch (e) { reject(e); } }, 0); }); } }); return promise2; } catch(errCallback) { return this.then(null, errCallback); } static reject(reason) { return new Promise((resolve, reject) => { reject(reason); }); } static resolve(value) { return new Promise((resolve, reject) => { resolve(value); }); } static all(values) { return new Promise((resolve, reject) => { let times = 0; const arr = []; function processMap(key, value) { arr[key] = value; if (++times === values.length) { resolve(arr); } } for (let i = 0; i < values.length; i++) { let val = values[i]; // 可能是promise,也可能是普通值 let then = val && val.then; if (typeof then === "function") { then.call( val, data => { // 获取成功的结果 processMap(i, data); }, reject ); } else { processMap(i, val); } } }); } static race(values) { return new Promise((resolve, reject) => { for (let i = 0; i < values.length; i++) { let p = values[i]; // p可能是promise 也可能是普通值 // 无论谁先成功就成功 谁先失败就失败 if (p instanceof Promise) { p.then(resolve, reject); } else { Promise.resolve(p).then(resolve, reject); } } }); }; finally(cb) { return this.then( y => { return Promise.resolve(cb()).then(() => y); }, r => { return Promise.resolve(cb()).then(() => { throw r; }); } ); } } module.exports = Promise;
这篇关于手撕promise源码及其原理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-07-04TiDB 资源管控的对撞测试以及最佳实践架构
- 2024-07-03万字长文聊聊Web3的组成架构
- 2024-07-02springboot项目无法注册到nacos-icode9专业技术文章分享
- 2024-06-26结对编程到底难不难?答案在这里
- 2024-06-19《2023版Java工程师》课程升级公告
- 2024-06-15matplotlib作图不显示3D图,怎么办?
- 2024-06-1503-Loki 日志监控
- 2024-06-1504-让LLM理解知识 -Prompt
- 2024-06-05做软件测试需要懂代码吗?
- 2024-06-0514-ShardingSphere的分布式主键实现