Promise源码指南
什么是Promise?
相信许多从事前端的小伙伴们都用过Promise,为了解决异步编程的弊端(地狱回调等问题),ES6提供了一个强大的东西,那就是Promise。Promise是将异步任务转换为同步任务的一个构造函数,通过resolve,reject改变任务的状态,必不可少的then方法用来收Promise的值,这些都是Promise的基本使用。那么Promise是如何处理状态的,又是如何实现resove,reject方法的,又是如何实现链式调用的呢,如果你不知道,那么这篇文章可以帮到你,下面我们一起来解析一下Promise到底是如何实现的,相信看完这篇文章,每个人都可以写出属于自己的Promise方法。
这里引入github上的一份符合Promise A+规范的源码 https://github.com/then/promise
函数对象Promise
我们先来看看src/index.js
这个文件
一些必要的定义
// 定义空函数
function noop() {}
// 用来存储错误信息
var IS_ERROR = {}
// 获取实例的then
function getThen(obj) {
try {
return obj.then;
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
}
// 执行then方法回调函数
function tryCallOne(fn, a) {
try {
return fn(a);
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
}
// 执行Promise构造函数
function tryCallTwo(fn, a, b) {
try {
fn(a, b);
} catch (ex) {
LAST_ERROR = ex;
return IS_ERROR;
}
}
Promise构造函数
function Promise(fn) {
// 校验实例
if (typeof this !== 'object') {
throw new TypeError('Promises must be constructed via new');
}
// 校验Promise的构造函数
if (typeof fn !== 'function') {
throw new TypeError('Promise constructor\'s argument is not a function');
}
// 存储的实例状态 0代表还未存储 1代表存储了1个 2代表存储了2个
this._deferredState = 0;
// Promise的状态 0代表padding 1代表Fulfilled 2代表Rejected 3代表resolve传入的是promise实例
this._state = 0;
// Fulfilled的值
this._value = null;
// 用来存储调用then后的实例
this._deferreds = null;
if (fn === noop) return;
// 处理Promise的参数
doResolve(fn, this);
}
//以下先不做解释
newPromise._onHandle = null;
newPromise._onReject = null;
newPromise._noop = noop;
当使用new
操作符实例化Promise的时候,必须传入Promise的构造函数fn,否则将抛出错误
然后初始化实例的状态和值
最后调用doResolve
方法
下面我们一起来看一下doResolve
这个方法
doResolve方法
function doResolve(fn, promise) {
// done防止重复触发
var done = false;
// tryCallTwo 用于处理并挂载reolve,reject方法
// 传入3个参数,Promise构造函数本身,resolve回调,reject回调
var res = tryCallTwo(fn, function (value) {
if (done) return;
done = true;
// 处理resolve方法
resolve(promise, value);
}, function (reason) {
if (done) return;
done = true;
// 处理reject方法
reject(promise, reason);
});
// 报错则直接reject
if (!done && res === IS_ERROR) {
done = true;
reject(promise, LAST_ERROR);
}
}
doResolve
方法接收两个参数(Promise的构造函数,Promise的实例也就是this)
这里的tryCallTwo
扮演了一个重要角色,它执行了构造函数fn
,传入三个参数(fn构造函数,resolve回调函数,reject回调函数)
我们回过头来看看tryCallTwo
这个函数
// 执行Promise构造函数
function tryCallTwo(fn, a, b) {
//...
fn(a, b);
//...
}
这里的a,b就是Promise的resolve
和reject
执行fn
并传入a
和b
然后resolve和reject又分别执行了resolve
方法和reject
方法
当实例调用了resolve之后就会执行resolve方法,下面来看看resove这个方法
resolve方法
function resolve(self, newValue) {
// 防止resolve的值传入实例本身
if (newValue === self) {
return reject(
self,
new TypeError('A promise cannot be resolved with itself.')
);
}
// 这里的if主要用于处理resolve一个Promise
if (
newValue &&
(typeof newValue === 'object' || typeof newValue === 'function')
) {
// 获取Promise的then方法
var then = getThen(newValue);
if (then === IS_ERROR) {
return reject(self, LAST_ERROR);
}
// 如果传入的是Promise的实例
if (
then === self.then &&
newValue instanceof newPromise
) {
self._state = 3;
// 直接把传入的Promise实例挂载到value
self._value = newValue;
finale(self);
return;
} else if (typeof then === 'function') { //如果传入带有then方法
// 把then当作构造函数并且把this指向这个then的对象
doResolve(then.bind(newValue), self);
return;
}
}
self._state = 1;
self._value = newValue;
finale(self);
}
resolve
方法一开始会先判断resolve传入的值,如果resolve传入的是一个「Promise实例」,将会进行以下处理
将实例的状态变为3
将实例的值变为传入的Promise实例
调用finale
方法
如果resolve传入的是Promise实例并且「包含then方法」则调用doResolve
执行这个实例的构造函数
如果resolve传入的是普通值而不是Promise实例,则做以下处理
将实例的状态变为1
将实例的值变为resolve传入的值
调用finale
方法
reject方法
function reject(self, newValue) {
// reject的时候状态变为2
self._state = 2;
// 保存错误信息到_value
self._value = newValue;
if (newPromise._onReject) {
newPromise._onReject(self, newValue);
}
finale(self);
}
当Promise执行reject
的时候此时状态变为2
保存reject
的错误信息到_value
调用finale
方法
finale方法
function finale(self) {
// 只调用一次then
if (self._deferredState === 1) {
handle(self, self._deferreds);
self._deferreds = null;
}
// 调用多次then
if (self._deferredState === 2) {
// console.log(self._deferreds);
for (var i = 0; i < self._deferreds.length; i++) {
handle(self, self._deferreds[i]);
}
self._deferreds = null;
}
}
finaale
方法在这里的作用就相当于handle
方法的中转站,根据不同的情况去调用handle
方法
上面有提到过_deferredState
是用来记录存储的实例状态
「同一个Promise对象下」,_deferredState
的值是随着then调用的次数决定的,为什么说只有在同一个Promise对象下才会触发呢,我们来看下面一个小例子
const promise2 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('哈哈哈')
}, 500);
})
.then(res => {}) //第一次then
.then(res => {}) //第二次then
到这里很多小伙伴会以为代码会在’if(self._deferredState === 2){}‘这个判断里面执行
事实上这样去调用then的情况下,_deferredState
的值永远只会=1,只会在’if(self._deferredState === 1){}‘的这个判断里面去执行
到这里小伙伴们又会说,不对啊,我这个不是在同一个Promise对象下吗,我不是只实例化了一次吗?
这里在使用Promise的时候确实是只实例化了一次,但是每次调用then方法返回的Promise跟实例的Promise并不是同一个引用,也就是说,这里的self并不是实例出来的对象,后面会详细介绍then是怎么返回Promise对象的
promise2.then(res => {
console.log(res);
})
promise2.then(res => {
console.log(res);
})
只有这样调用then,才会执行’if(self._deferredState === 2){}‘这个判断
Promise.prototype.then
Promise.prototype.then = function(onFulfilled, onRejected) {
if (this.constructor !== Promise) {
return safeThen(this, onFulfilled, onRejected);
}
// 新建一个promise实例
var res = new Promise(noop);
// // new Handler 构建了一个包含新的promise实例和resolve,reject方法的对象
handle(this, new Handler(onFulfilled, onRejected, res));
// 每次then处理完之后返回一个新的promise实例
return res;
};
接收两个参数resolve
和reject
函数
一开始先判断实例的构造函数是不是Promise(防止外部修改prototype.constructor)
这里的safeThen
函数不作过多解释,主要用于实例化外部实例的构造函数并返回实例
创建一个空的Promise实例给res
我们看看下面这句代码主要做了什么
handle(this, new Handler(onFulfilled, onRejected, res));
我们拆开来看,先看看new Handler
实例化得到了什么
// 这个函数的作用就是每次then的时候通过这个构造函数根据resolve和reject构建一个新的对象
function Handler(onFulfilled, onRejected, promise){
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
this.promise = promise;
}
由此可见通过实例化Handler
函数可以得到一个大概长这样子的对象
所以handle
函数传入了2个参数,一个是this,一个是像这样子的对象,那么,接下来就可以看handle
函数做了什么事情了
function handle(self, deferred) {
// resolve传入的是promise的实例,this(上下文)则改成传入的promise实例
while (self._state === 3) {
self = self._value;
}
if (newPromise._onHandle) {
newPromise._onHandle(self);
}
// 当padding状态时(没有调用resolve也没有调用reject)
// 存储新的promise实例
if (self._state === 0) {
if (self._deferredState === 0) {
self._deferredState = 1;
self._deferreds = deferred;
return;
}
// 已经调用过1次then
if (self._deferredState === 1) {
self._deferredState = 2;
self._deferreds = [self._deferreds, deferred];
return;
}
// 多次then
self._deferreds.push(deferred);
return;
}
handleResolved(self, deferred);
}
看上面的代码注释大家都应该知道了,handle
函数主要就是用作修改_deferredState
的值和保存每次then生成的新实例
最后return res
每次调用then方法都返回一个新的Promise实例
链式调用
细心的你可能已经发现了在前面的handle方法里的最下面调用了一个名为handleResolved
的函数,话不多说直接上代码
function handleResolved(self, deferred) {
// asap(function() {
// var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
// if (cb === null) {
// if (self._state === 1) {
// resolve(deferred.promise, self._value);
// } else {
// reject(deferred.promise, self._value);
// }
// return;
// }
// var ret = tryCallOne(cb, self._value);
// if (ret === IS_ERROR) {
// reject(deferred.promise, LAST_ERROR);
// } else {
// resolve(deferred.promise, ret);
// }
// });
// resolve后,获取then的回调
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
// 如果then没有回调,则手动调用回调
if (cb === null) {
if (self._state === 1) {
resolve(deferred.promise, self._value);
} else {
reject(deferred.promise, self._value);
}
return;
}
// 获取then的返回值
var ret = tryCallOne(cb, self._value);
if (ret === IS_ERROR) {
reject(deferred.promise, LAST_ERROR);
} else {
resolve(deferred.promise, ret);
}
}
其实源码还引入了asap这个库,我先把这个asap函数注释掉了,理由是懒得用npm,本文全程是html+js+live-server
但是大家千万不要忽略asap
这个函数!!!
纵观全文到现在,大家好像并没有发现源码有一点点异步的信息,大家都知道Promise是异步执行的,就是靠asap
函数,通过setImmediate
这个核心方法去异步执行asap里面的东西,有兴趣的可以去翻翻asap的源码看下具体是怎么实现的
这里只是为了更好的解析源码,没有asap那么Promise就没有意义了,ok我们回归正文
这里就很好理解了
通过tryCallOne
函数得到then的返回值
然后再次调用resolve
,如果报错或者手动调用reject则调用reject,这样就完成了Promise的链式调用
扩展
src/es6-extensions.js
定义
var TRUE = valuePromise(true);
var FALSE = valuePromise(false);
var NULL = valuePromise(null);
var UNDEFINED = valuePromise(undefined);
var ZERO = valuePromise(0);
var EMPTYSTRING = valuePromise('');
function valuePromise(value) {
var p = new newPromise(newPromise._noop);
p._state = 1;
p._value = value;
return p;
}
Promise.resolve
Promise.resolve = function (value) {
// 判断value是否是Promise得实例
if (value instanceof Promise) return value;
// 因为0,'',null等会被隐式转换成false,所以这里做了精确判断
if (value === null) return NULL;
if (value === undefined) return UNDEFINED;
if (value === true) return TRUE;
if (value === false) return FALSE;
if (value === 0) return ZERO;
if (value === '') return EMPTYSTRING;
// 这里的判断跟之前resolve方法一样都是判断传入的是否是一个Promise
if (typeof value === 'object' || typeof value === 'function') {
try {
var then = value.then;
if (typeof then === 'function') {
return new Promise(then.bind(value));
}
} catch (ex) {
return new Promise(function (resolve, reject) {
reject(ex);
});
}
}
// 根据valuePromise方法返回一个新的Promise
return valuePromise(value);
};
try/catch 用于捕获Promise.resolve传入的val是否包含then
方法
Promise.all
Promise.all = function (arr) {
var args = iterableToArray(arr);
return new Promise(function (resolve, reject) {
if (args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {
if (val && (typeof val === 'object' || typeof val === 'function')) {
// 如果val是Promise的实例
if (val instanceof Promise && val.then === Promise.prototype.then) {
// _state等于3 证明val实例的值也是一个Promise实例,把val替换成新的Promise实例
while (val._state === 3) {
val = val._value;
}
// resolved成功调用,递归处理resolved的值
if (val._state === 1) return res(i, val._value);
if (val._state === 2) reject(val._value);
// 处于padding状态时调用then方法并手动处理值
val.then(function (val) {
res(i, val);
}, reject);
return;
} else {
// 如果不是promise的实例且包含then方法
var then = val.then;
if (typeof then === 'function') {
var p = new Promise(then.bind(val));
p.then(function (val) {
res(i, val);
}, reject);
return;
}
}
}
args[i] = val;
// promise.all里面全部为fulFilled状态后
if (--remaining === 0) {
resolve(args);
}
}
for (var i = 0; i < args.length; i++) {
res(i, args[i]);
}
});
};
第一行用到了iterableToArray
函数,这个函数的主要作用是把类数组转换成可被遍历的数组
const p1 = new Promise(() => {})
const p2 = new Promise(() => {})
console.log(Array.isArray(Promise.all[p1,p2])) //false
// 兼容Array.form这个es6语法
var iterableToArray = function (iterable) {
if (typeof Array.from === 'function') {
// ES2015+, iterables exist
iterableToArray = Array.from;
return Array.from(iterable);
}
// ES5, only arrays and array-likes exist
iterableToArray = function (x) { return Array.prototype.slice.call(x); };
return Array.prototype.slice.call(iterable);
}
使用Array.prototype.slice.call(x)对Array.from做了兼容
Promise.all = function (arr) {
var args = iterableToArray(arr);
return new Promise(function (resolve, reject) {
if (args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {
...
}
for (var i = 0; i < args.length; i++) {
res(i, args[i]);
}
});
};
先不要看res
方法,先看看怎么处理传进来的参数
循环调用res
处理传进来的每一项,第一个参数为下标,第二个参数是数组的每一项
再来看下res
方法
function res(i, val) {
if (val && (typeof val === 'object' || typeof val === 'function')) {
// 如果val是Promise的实例
if (val instanceof Promise && val.then === Promise.prototype.then) {
// _state等于3 证明val实例的值也是一个Promise实例,把val替换成新的Promise实例
while (val._state === 3) {
val = val._value;
}
// resolved成功调用,递归处理resolved的值
if (val._state === 1) return res(i, val._value);
if (val._state === 2) reject(val._value);
// 处于padding状态时调用then方法并手动处理值
val.then(function (val) {
res(i, val);
}, reject);
return;
} else {
// 如果不是promise的实例且包含then方法
var then = val.then;
if (typeof then === 'function') {
var p = new Promise(then.bind(val));
p.then(function (val) {
res(i, val);
}, reject);
return;
}
}
}
args[i] = val;
// promise.all里面全部为fulFilled状态后
if (--remaining === 0) {
resolve(args);
}
}
如果传进来的val
(args的每一项)不是对象或者function的话,那么直接视为结果值把args[i]给替换掉
args[i] = val;
如果传进来的是一个Promise
,则
if (val instanceof Promise && val.then === Promise.prototype.then) {
...
}
如果传进来的val
不是Promise
且包含then方法,则
else {
// 如果不是promise的实例且包含then方法
var then = val.then;
if (typeof then === 'function') {
var p = new newPromise(then.bind(val));
p.then(function (val) {
res(i, val);
}, reject);
return;
}
}
重点在这一段
// _state等于3 证明val实例的值也是一个Promise实例,把val替换成新的Promise实例
while (val._state === 3) {
val = val._value;
}
// resolved成功调用,递归处理resolved的值
if (val._state === 1) return res(i, val._value);
if (val._state === 2) reject(val._value);
// 处于padding状态时调用then方法并手动处理值
val.then(function (val) {
res(i, val);
}, reject);
return;
只有当Promise的状态为Fulfilled
的时候,实例的value
才会被正确的处理,否则会执行return
,所以只要有一个Promise
未能成功Fulfilled
都不会执行resolve(args)
//满足不了条件
if (--remaining === 0) {
resolve(args);
}
当得到所有结果值的时候(args[i] = val) == args[i] = val.value
,调用resolve
方法并传入结果数组args
看个例子
const promise2 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('哈哈哈')
}, 700);
})
const promise3 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('哈哈哈2')
}, 600);
})
newPromise.all([promise2,promise3])
.then(res => {
console.log(res); //['哈哈哈','哈哈哈2']
})
这里无论什么时候resolve
,最后得出的结果数组res都是按照Promise.all([])
数组里面的顺序来输出的,这也印证了源码中为什么要把下标传入到res()
的原因(res(i, args[i]))
Promise.race
Promise.race = function (values) {
return new Promise(function (resolve, reject) {
iterableToArray(values).forEach(function(value){
Promise.resolve(value).then(resolve, reject);
});
});
};
Promise.race
传入一个数组,用Promise.resolve
处理每一项并调用then方法
最后返回一个Promise
实例
这里的Promise.resolve(value).then(resolve, reject)
当传入的Promise
谁的状态首先变为Fulfilled
,谁就先调用then
因为Promise
执行resolve
一次之后状态就不会改变,所以race传入多个Promise
,谁的状态先变为Fulfilled
,race就返回哪个
const promise2 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('哈哈哈')
}, 700);
})
const promise3 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('哈哈哈2')
}, 600);
})
Promise.race([promise2,promise3])
.then(res => {
console.log(res); //哈哈哈2
})
如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:
点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-) 欢迎加我微信「TH0000666」一起交流学习... 关注公众号「前端Sharing」,持续为你推送精选好文。