以 Promise/A+ 作为标准,编写一个可经过标准测试的Promise类库。git
const REJECTED = 'rejected';
const RESOLVED = 'resolved';
const PENDING = 'pending';
class MPromise{
constructor(resolver){
if(resolver && typeof resolver !== 'function'){
throw new Error('MPromise resolver is not function');
}
this.state = PENDING; //当前promise对象的状态
this.data = undefined; //当前promise对象的数据(成功或失败)
this.callbackQueue = []; //当前promise对象注册的回调队列
if(resolver){
executeResolver.call(this, resolver);
}
}
then(){
//todo
}
}
复制代码
因此,一个 Promise构造函数 和一个实例方法then 就是Promise的核心的了,其它的都是Promise的语法糖或者说是扩展github
构造器的初始化,使用 new Promise(function(resolve, reject){...})
实例化 Promise 时,去改变promise的状态,是执行 resolve() 或 reject()方法,那么,resolver的两个参数分别是成功的操做函数和失败的操做函数。promise
function executeResolver(resolver){
let called = false; //状态变动不可逆
let _this = this;
function onError(reason){
if(!called){
return;
}
called = true;
executeCallback.call(_this,'reject', reason);
}
function onSuccess(value){
if(!called){
return;
}
called = true;
executeCallback.call(_this, 'resolve', value);
}
try{
resolver(onSuccess, onError);
}catch(e){
onError(e);
}
}
复制代码
这里将上面执行 resolver 的方法抽象出来,内部再将 resovle 和 reject 两个参数包装成 成功和失败的回调。浏览器
由于执行 resolve() 或 reject() 内部主要做用是更改当前实例的状态为 rejected 或 resolved,而后执行当前实例 then() 中注册的 成功或失败的回调函数, 因此从过程上来看,大体是相同的,抽象出来共用安全
function executeCallback(type, x){
const isResolve = type === 'resolve' ? true : false;
let thenable;
if(isResolve && typeof x === 'object' && typeof x.then === 'function'){
try {
thenable = getThen(x);
} catch (e) {
return executeCallback.call(this, 'reject', e);
}
}
if(isResolve && thenable){
executeResolver.call(this, thenable); //注意是this
} else {
this.state = isResolve ? RESOLVED : REJECTED;
this.data = x;
this.callbackQueue.forEach(v => v[type](x));
}
return this;
}
复制代码
function getThen(obj){
const then = obj && obj.then;
if(obj && typeof obj === 'object' && typeof then === 'function'){
return applyThen(){
then.apply(obj, arguments)
}
}
}
复制代码
标准中规定:bash
class MPromise{
...
then(onResolved, onRejected){
//回调不是函数,能够忽略
if(this.state === RESOLVED && onResolved !== 'function'
|| this.state === REJECTED && onRejected !== 'function'){
return this;
}
let promise = new MPromise();
if(this.state !== PENDING){
var callback = this.state === RESOLVED ? onResolved : onRejected;
//注意:传入promise,
//异步调用
executeCallbackAsync.call(promise, callback, this.data);
} else {
this.callbackQueue.push(new CallbackItem(promise, onResolved, onRejected))
}
return promise; //必须返回promise,才能链式调用
}
}
复制代码
上面将异步调用callback的逻辑抽象成了一个方法executeCallbackAsync ,这个方法主要功能是安全的执行callback方法:app
function executeCallbackAsync(callback, value){
let _this = this;
setTimeout(() => {
let res;
try{
res = callback(value);
}catch(e){
return executeCallback.call(_this, 'reject', e);
}
if(res !== _this){
return executeCallback.call(_this, 'resolve', res);
} else {
return executeCallback.call(_this, 'reject', new TypeError('Cannot resolve promise with itself'));
}
}, 4);
}
复制代码
注意这里最好不要用 setTimeout ,使用 setTimeout 能够异步执行回调,但其实并非真正的异步线程,而是利用了浏览器的 Event Loop 机制去触发执行回调,而浏览器的事件轮循时间间隔是 4ms ,因此链接的调用 setTimeout 会有 4ms 的时间间隔,而在Nodejs 中的 Event Loop 时间间隔是 1ms,因此会产生必定的延迟,若是promise链比较长,延迟就会越明显,这里能够引入NPM上的 immediate 模块来异步无延迟的执行回调。异步
上面then中对于回调的处理,使用了一个回调对象来管理注册的回调,将回调按顺序添加至 callbackQueue 队列中,调用时,依次调用。函数
class CallbackItem {
constructor(promise, onResolved, onRejected) {
this.promise = promise;
this.onResolved = typeof onResolved === 'function' ? onResolved : function (v) {
return v;
};
this.onRejected = typeof onRejected === 'function' ? onRejected : function (v) {
throw v;
};
}
resolve(value) {
executeCallbackAsync.call(this.promise, this.onResolved, value);
}
reject(value) {
executeCallbackAsync.call(this.promise, this.onRejected, value);
}
}
复制代码
以上参考深刻理解Promise中的文章,实现MPromiseoop
function fn() {
let promise1 = new MPromise((resolve, reject) => {
resolve(1);
});
new MPromise((resolve, reject) => {
resolve(promise1); //系统执行promise1.then
}).then(res => {
console.log(res);
return 222;
}).catch(err => {
console.log(err);
});
}
fn(); // 1
复制代码