GitHub前端
Promise是前端大厂面试的一道常考题,掌握Promise用法及其相关原理,对你的面试必定有很大帮助。这篇文章主要讲解Promise源码实现,若是你尚未掌握Promise的功能和API,推荐你先去学习一下Promise的概念和使用API,学习知识就要脚踏实地,先把基础搞好才能深入理解源码的实现。
这里推荐阮一峰老师的文章 git
ES6入门-Promise对象 es6
若是你已经掌握了Promise的基本用法,咱们进行下一步github
说到Promise/A+规范,不少同窗可能很不理解这是一个什么东西,下面给出两个地址,不了解的同窗须要先了解一下,对咱们后续理解源码颇有帮助,先看两遍,有些地方看不懂也不要紧,后续咱们能够经过源码来回头再理解,想把一个知识真的学会,就要反复琢磨,从【确定->否认->再确定】不断地深刻理解,直到彻底掌握。 面试
Promise/A+规范英文地址
Promise/A+规范中文翻译 算法
若是你看过了Promise/A+规范,咱们继续,我会带着你们按照规范要求,一步一步的来实现源码npm
一个promise必须处于三种状态之一: 请求态(pending), 完成态(fulfilled),拒绝态(rejected)数组
2.1.2.2 必须有一个值,且此值不能改变promise
咱们先找需求来完成这一部分代码,一个简单的小架子异步
// 2.1 状态常量 const PENDING = 'pending'; const RESOLVED = 'resolved'; const REJECTED = 'rejected'; // Promise构造函数 function MyPromise(fn) { const that = this; this.state = PENDING; this.value = null; this.resolvedCallbacks = []; this.rejectedCallbacks = []; function resolve() { if (that.state === PENDING) { } } function reject() { if (that.state === PENDING) { } } }
上面这段代码完成了Promise构造函数的初步搭建,包含:
下面咱们来完成resolve和reject
function resolve(value) { if (that.state === PENDING) { that.state = RESOLVED that.value = value that.resolvedCallbacks.map(cb => cb(that.value)) } } function reject(value) { if (that.state === PENDING) { that.state = REJECTED that.value = value that.rejectedCallbacks.map(cb => cb(that.value)) } }
记下来咱们须要来执行新建Promise传入的函数体
try { fn(resolve, reject); } catch (e){ reject(e) }
在执行过程当中可能会遇到错误,须要捕获错误传给reject
promise必须提供then方法来存取它当前或最终的值或者缘由。
promise的then方法接收两个参数:
promise.then(onFulfilled, onRejected)
现根据这些要求咱们先实现个简单的then函数:
MyPromise.prototype.then = function (onFulfilled, onRejected) { const that = this onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r } if (that.state === PENDING) { that.resolvedCallbacks.push(onFulfilled) that.rejectedCallbacks.push(onRejected) } if (that.state === RESOLVED) { onFulfilled(that.value) } if (that.state === REJECTED) { onRejected(that.value) } }
如上咱们就完成了一个简易版的promise,可是还不能彻底知足Promise/A+规范,接下来咱们继续完善
— 2.2.6.1 若是/当 promise 完成执行(fulfilled),各个相应的onFulfilled回调 必须根据最原始的then 顺序来调用
— 2.2.6.2 若是/当 promise 被拒绝(rejected),各个相应的onRejected回调 必须根据最原始的then 顺序来调用
promise2 = promise1.then(onFulfilled, onRejected);
接下来根据规范需求继续完善then函数里的代码:
if (that.status === 'PENDING') { promise2 = new Promise(function (resolve, reject) { that.resolvedCallbacks.push(function () { setTimeout(function () { try { let x = onFulfilled(that.value); resolutionProcedure(promise2, x, resolve, reject) } catch (e) { reject(e); } }) }); that.rejectedCallbacks.push(function () { setTimeout(function () { try { let x = onRejected(that.value); resolutionProcedure(promise2, x, resolve, reject) } catch (e) { reject(e); } }) }); }) } if (that.status === 'RESOLVED') { promise2 = new Promise(function (resolve, reject) { setTimeout(function () { //用setTimeOut实现异步 try { let x = onFulfilled(that.value); //x多是普通值 也多是一个promise, 还多是别人的promise resolutionProcedure(promise2, x, resolve, reject) //写一个方法统一处理 } catch (e) { reject(e); } }) }) } if (that.status === 'REJECTED') { promise2 = new Promise(function (resolve, reject) { setTimeout(function () { try { let x = onRejected(that.value); resolutionProcedure(promise2, x, resolve, reject) } catch (e) { reject(e); } }) }) }
2.3.3.3 若是then是一个方法,把x看成this来调用它, 第一个参数为 resolvePromise,第二个参数为rejectPromise,其中:
2.3.3.3.4 若是调用then抛出一个异常e,
若是一个promise被一个thenable resolve,而且这个thenable参与了循环的thenable环,
[[Resolve]](promise, thenable)的递归特性最终会引发[[Resolve]](promise, thenable)再次被调用。
遵循上述算法会致使无限递归,鼓励(但不是必须)实现检测这种递归并用包含信息的TypeError做为reason拒绝(reject)
这部分规范主要描述了resolutionProcedure函数的规范,下面咱们来实现resolutionProcedure这个函数,我先我么你关注2.3.4下面那段话,简单的来讲规定了x不能与promise2相等,这样会发生循环引用的问题,以下栗子:
let p = new MyPromise((resolve, reject) => { resolve(1) }) let p1 = p.then(value => { return p1 })
因此咱们须要先进行检测,代码以下:
function resolutionProcedure(promise2, x, resolve, reject) { if (promise2 === x) { return reject(new TypeError('Error')) } }
接下来咱们判断x的类型
if (x instanceof MyPromise) { x.then(function (value) { resolutionProcedure(promise2, value, resolve, reject) }, reject) }
若是 x 为 Promise 的话,须要判断如下几个状况:
最后咱们来完成剩余的代码:
let called = false if (x !== null && (typeof x === 'object' || typeof x === 'function')) { try { let then = x.then if (typeof then === 'function') { then.call( x, y => { if (called) return called = true resolutionProcedure(promise2, y, resolve, reject) }, e => { if (called) return called = true reject(e) } ) } else { resolve(x) } } catch (e) { if (called) return called = true reject(e) } } else { resolve(x) }
有专门的测试脚本能够测试所编写的代码是否符合PromiseA+的规范
首先,在promise实现的代码中,增长如下代码:
Promise.defer = Promise.deferred = function () { let dfd = {}; dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve; dfd.reject = reject; }); return dfd; }
安装测试脚本:
npm install -g promises-aplus-tests
若是当前的promise源码的文件名为promise.js
那么在对应的目录执行如下命令:
promises-aplus-tests promise.js
共有872条测试用例,能够完美经过
这样咱们就完成了符合Promise/A+规范的源码,下面是整个代码:
const PENDING = 'pending'; const RESOLVED = 'resolve'; const REJECTED = 'rejected'; function Promise(fn) { let that = this; that.status = 'PENDING'; that.value = undefined; that.resolvedCallbacks = []; that.rejectedCallbacks = []; function resolve(value) { if (value instanceof Promise) { return value.then(resolve, reject) } if (that.status === 'PENDING') { that.status = 'RESOLVED'; that.value = value; that.resolvedCallbacks.map(cb => cb(that.value)); } } function reject(value) { if (that.status === 'PENDING') { that.status = 'REJECTED'; that.value = value; that.rejectedCallbacks.map(cb => cb(that.value)); } } try { fn(resolve, reject); } catch (e) { reject(e); } } function resolutionProcedure(promise2, x, resolve, reject) { //有可能这里返回的x是别人的promise 要尽量容许其余人乱写 if (promise2 === x) {//这里应该报一个循环引用的类型错误 return reject(new TypeError('循环引用')); } //看x是否是一个promise promise应该是一个对象 let called; //表示是否调用过成功或者失败 if (x !== null && (typeof x === 'object' || typeof x === 'function')) { //多是promise 看这个对象中是否有then 若是有 姑且做为promise 用try catch防止报错 try { let then = x.then; if (typeof then === 'function') { //成功 then.call(x, function (y) { if (called) return //避免别人写的promise中既走resolve又走reject的状况 called = true; resolutionProcedure(promise2, y, resolve, reject) }, function (err) { if (called) return called = true; reject(err); }) } else { resolve(x) //若是then不是函数 则把x做为返回值. } } catch (e) { if (called) return called = true; reject(e) } } else { //普通值 return resolve(x) } } Promise.prototype.then = function (onFulfilled, onRejected) { //成功和失败默认不传给一个函数 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) { return value; } onRejected = typeof onRejected === 'function' ? onRejected : function (err) { throw err; } let that = this; let promise2; //新增: 返回的promise if (that.status === 'PENDING') { promise2 = new Promise(function (resolve, reject) { that.resolvedCallbacks.push(function () { setTimeout(function () { try { let x = onFulfilled(that.value); resolutionProcedure(promise2, x, resolve, reject) } catch (e) { reject(e); } }) }); that.rejectedCallbacks.push(function () { setTimeout(function () { try { let x = onRejected(that.value); resolutionProcedure(promise2, x, resolve, reject) } catch (e) { reject(e); } }) }); }) } if (that.status === 'RESOLVED') { promise2 = new Promise(function (resolve, reject) { setTimeout(function () { //用setTimeOut实现异步 try { let x = onFulfilled(that.value); //x多是普通值 也多是一个promise, 还多是别人的promise resolutionProcedure(promise2, x, resolve, reject) //写一个方法统一处理 } catch (e) { reject(e); } }) }) } if (that.status === 'REJECTED') { promise2 = new Promise(function (resolve, reject) { setTimeout(function () { try { let x = onRejected(that.value); resolutionProcedure(promise2, x, resolve, reject) } catch (e) { reject(e); } }) }) } return promise2; } Promise.defer = Promise.deferred = function () { let dfd = {}; dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve; dfd.reject = reject; }); return dfd; } module.exports = Promise;
以上就是符合Promise/A+规范的源码,ES6的Promise其实并非向咱们这样经过js来实现,而是在底层实现,而且还扩展了不少新的方法:
这里就不一一介绍啦,你们能够参考阮一峰老师的文章 ES6入门-Promise对象
这篇文章给你们讲解的Promise/A+规范的源码,但愿你们能多读多写,深入的体会一下源码的思想,对之后的开发也颇有帮助。
感谢你们的阅读,以为还不错,辛苦点一下关注,谢谢!