手写Promise一直是前端童鞋很是头疼的问题,也是面试的高频题。网上有不少手写Promise的博客,但大部分都存在或多或少的问题。
下面咱们根据A+规范,手写一个Promise前端
在此部分,先把Promise的基础结构写出来。
直接上代码node
// 规范2.1:A promise must be in one of three states: pending, fulfilled, or rejected. // 三个状态:PENDING、FULFILLED、REJECTED const PENDING = Symbol(); const FULFILLED = Symbol(); const REJECTED = Symbol(); // 根据规范2.2.1到2.2.3 class _Promise { constructor(executor) { // 默认状态为 PENDING this.status = PENDING; // 存放成功状态的值,默认为 undefined this.value = undefined; // 存放失败状态的值,默认为 undefined this.reason = undefined; // 成功时,调用此方法 let resolve = (value) => { // 状态为 PENDING 时才能够更新状态,防止 executor 中调用了两次 resovle/reject 方法 if (this.status === PENDING) { this.status = FULFILLED; this.value = value; } }; // 失败时,调用此方法 let reject = (reason) => { // 状态为 PENDING 时才能够更新状态,防止 executor 中调用了两次 resovle/reject 方法 if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; } }; try { // 当即执行,将 resolve 和 reject 函数传给使用者 executor(resolve, reject); } catch (error) { // 发生异常时执行失败逻辑 reject(error); } } // 包含一个 then 方法,并接收两个参数 onFulfilled、onRejected then(onFulfilled, onRejected) { if (this.status === FULFILLED) { onFulfilled(this.value); } if (this.status === REJECTED) { onRejected(this.reason); } } } export default _Promise;
接下来咱们用测试代码测一下面试
const promise = new _Promise((resolve, reject) => { resolve('成功'); setTimeout(() => { console.log('settimeout1'); }, 0); }) .then( (data) => { console.log('success', data); setTimeout(() => { console.log('settimeout2'); }, 0); }, (err) => { console.log('faild', err); } ) .then((data) => { console.log('success2', data); });
控制台打印
能够看到,在executor方法中的异步行为在最后才执行
并且若是把resolve方法放到setTimeout中,会没法执行
这固然是不妥的。
接下来咱们优化一下异步数组
在上一小节中,咱们将resolve的结果值存放到了this.value里。
优化后的代码以下:promise
// 规范2.1:A promise must be in one of three states: pending, fulfilled, or rejected. // 三个状态:PENDING、FULFILLED、REJECTED const PENDING = Symbol(); const FULFILLED = Symbol(); const REJECTED = Symbol(); class _Promise { constructor(executor) { this.status = PENDING; this.value = undefined; this.reason = undefined; // 存放成功的回调 this.onResolvedCallbacks = []; // 存放失败的回调 this.onRejectedCallbacks = []; // 这里使用数组,是由于若是屡次调用then,会把方法都放到数组中。 // 可是目前这个版本还不支持then的链式调用 let resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED; this.value = value; // 依次将对应的函数执行 // 在此版本中,这个数组实际上长度最多只为1 this.onResolvedCallbacks.forEach(fn => fn()); } } let reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; // 依次将对应的函数执行 // 在此版本中,这个数组实际上长度最多只为1 this.onRejectedCallbacks.forEach(fn => fn()); } } try { executor(resolve, reject) } catch (error) { reject(error) } } then(onFulfilled, onRejected) { if (this.status === FULFILLED) { onFulfilled(this.value) } if (this.status === REJECTED) { onRejected(this.reason) } // 上面两个分支是:支持resolve函数执行的时候,若是不在异步行为里执行resolve的话,会当即执行onFulfilled方法 if (this.status === PENDING) { // 若是promise的状态是 pending,须要将 onFulfilled 和 onRejected 函数存放起来,等待状态肯定后,再依次将对应的函数执行 this.onResolvedCallbacks.push(() => { onFulfilled(this.value) }); // 若是promise的状态是 pending,须要将 onFulfilled 和 onRejected 函数存放起来,等待状态肯定后,再依次将对应的函数执行 this.onRejectedCallbacks.push(() => { onRejected(this.reason); }) } } }
咱们用测试方法测一下:浏览器
const promise = new _Promise((resolve, reject) => { setTimeout(() => { console.log('settimeout1'); resolve('成功'); }, 0); }) .then( (data) => { console.log('success', data); setTimeout(() => { console.log('settimeout2'); }, 0); return data; }, (err) => { console.log('faild', err); } ) .then((data) => { console.log('success2', data); });
控制台结果:
能够看到,异步顺序是正确的,先执行settimeout1,再执行success
可是不支持链式的then调用,也不支持在then中返回一个新的Promisedom
接下来咱们将完整实现一个支持链式调用的Promis异步
// 规范2.1:A promise must be in one of three states: pending, fulfilled, or rejected. // 三个状态:PENDING、FULFILLED、REJECTED const PENDING = Symbol(); const FULFILLED = Symbol(); const REJECTED = Symbol(); class _Promise { constructor(executor) { this.status = PENDING; this.value = undefined; this.reason = undefined; // 存放成功的回调 this.onResolvedCallbacks = []; // 存放失败的回调 this.onRejectedCallbacks = []; // 这里使用数组,是由于若是屡次调用then,会把方法都放到数组中。 // 可是目前这个版本还不支持then的链式调用 let resolve = (value) => { if (this.status === PENDING) { this.status = FULFILLED; this.value = value; // 依次将对应的函数执行 // 在此版本中,这个数组实际上长度最多只为1 this.onResolvedCallbacks.forEach(fn => fn()); } } let reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED; this.reason = reason; // 依次将对应的函数执行 // 在此版本中,这个数组实际上长度最多只为1 this.onRejectedCallbacks.forEach(fn => fn()); } } try { // 当即执行executor方法 executor(resolve, reject) } catch (error) { reject(error) } } // 这里就是最关键的then方法 then(onFulfilled, onRejected) { // 克隆this,由于以后的this就不是原promise的this了 const self = this; // 判断两个传入的方法是否是funcion,若是不是,那么给一个function的初始值 onnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function'?onRejected:reason => { throw new Error(reason instanceof Error ? reason.message:reason) } // 返回一个新的promise,剩下的逻辑都在这个新的promise里进行 return new _Promise((resolve, reject) => { if (this.status === PENDING) { // 若是promise的状态是 pending,须要将 onFulfilled 和 onRejected 函数存放起来,等待状态肯定后,再依次将对应的函数执行 self.onResolvedCallbacks.push(() => { // 使用settimeout模拟微任务 setTimeout((0 => { // self.value是以前存在value里的值 const result = onFulfilled(self.value); // 这里要考虑两种状况,若是onFulfilled返回的是Promise,则执行then // 若是返回的是一个值,那么直接把值交给resolve就行 result instanceof _Promise ? result.then(resolve, reject) : resolve(result); }, 0) onFulfilled(self.value) }); // 若是promise的状态是 pending,须要将 onFulfilled 和 onRejected 函数存放起来,等待状态肯定后,再依次将对应的函数执行 // reject也要进行同样的事 self.onRejectedCallbacks.push(() => { setTimeout(() => { const result = onRejected(self.reason); // 不一样点:此时是reject result instanceof _Promise ? result.then(resolve, reject) : reject(result); }, 0) }) } // 若是不是PENDING状态,也须要判断是否是promise的返回值 if (self.status === FULFILLED) { setTimeout(() => { const result = onFulfilled(self.value); result instanceof _Promise ? result.then(resolve, reject) : resolve(result); }); } if (self.status === REJECTED) { setTimeout(() => { const result = onRejected(self.reason); result instanceof _Promise ? result.then(resolve, reject) : reject(result); }) } }) // 到这里,最难的then方法已经写完了 } }
catch方法的通常用法是new _Promise(() => {...}).then(() => {...}).catch(e => {...})
因此它是一个和then同级的方法,它实现起来很是简单:函数
class _Promise{ ... catch(onRejected) { return this.then(null, onRejected); } }
静态resolve、静态reject的用法:_Promise.resolve(() => {})
这样能够直接返回一个_Promise
这块的实现,参考then中返回_Promise的那一段,就能实现
reject相似测试
class _Promise{ ... static resolve(value) { if (value instanceof Promise) { // 若是是Promise实例,直接返回 return value; } else { // 若是不是Promise实例,返回一个新的Promise对象,状态为FULFILLED return new Promise((resolve, reject) => resolve(value)); } } static reject(reason) { return new Promise((resolve, reject) => { reject(reason); }) } }
最后再说一个关于微任务的
setTimeout毕竟是个宏任务,咱们能够用MutationObserver来模拟一个微任务,只要将下面的nextTick方法替换setTimeout方法便可
function nextTick(fn) { if (process !== undefined && typeof process.nextTick === "function") { return process.nextTick(fn); } else { // 实现浏览器上的nextTick var counter = 1; var observer = new MutationObserver(fn); var textNode = document.createTextNode(String(counter)); observer.observe(textNode, { characterData: true, }); counter += 1; textNode.data = String(counter); } }
这个方法的原理不难看懂,就是在dom里建立了一个textNode,用MutationObserver监控这个node的变化。在执行nextTick方法的时候手动修改这个textNode,触发MutationObserver的callback,这个callback就会在微任务队列中执行。
注意MutationObserver的兼容性。
我我的感受完整理解Promise的源码仍是比较考验代码功底的,一开始建议把源码放在编译器里一点一点调试着看,若是实在不知道怎么下手,也能够把代码背下来,慢慢咀嚼。实际上,背下来以后,人脑对这个东西会有一个缓慢的理解过程,到了某一天会感受恍然大悟。