根据Promise A+规范实现一个Promise

229 阅读7分钟

Promise对象

Promise兼容性

Promise A+规范

Promise A+规范的测试

自我实现的Promise是不会变成微任务的,只能通过定时器去模拟。微任务是浏览器自己实现的。

Promise的特点

  1. 对象的状态不受外界影响;
  2. 一旦状态改变就不会在变,任何时候都可以得到这个结果;
  3. Promise将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
  • 缺点:
    1. 无法取消promise,一旦创建它就会立即执行,无法中途取消;
    2. 如果不设置回调函数,Promise内部抛出的错误不会反应到外部;
    3. 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

简易版Promise

  1. Promise是一个类;
  2. Promise有三个状态 Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败);
  3. Promise的成功还是失败,用户自定义;
  4. Promise默认执行器(executor)立即执行;
  5. 每个Promise都有then方法,接手两个参数,一个是成功的回调,一个是失败的回调;
  6. 执行函数时发生异常也会执行失败的逻辑(try..catch...)
  7. 一旦成功就不能失败,反之亦然(只有等待态才能更改状态)。
const PENDING = 'pending'; // 等待态
const RESOLVED = 'resolved'; // 成功态
const REJECTED = 'rejected'; // 失败态

class Promise {
    constructor(executor) {
    	this.status = PENDING; // 默认状态
    	this.value = undefined; // 成功返回值
    	this.reason = undefined; // 失败返回值
    	const resolve = value => {
            if (this.status === PENDING) {
            	this.status = RESOLVED;
            	this.value = value;
            }
    	};
    	const reject = reason => {
            if (this.status === PENDING) {
            	this.status = REJECTED;
            	this.reason = reason;
            }
    	};
    	try {
            executor(resolve, reject);
    	} catch (err) {
            reject(err);
    	}
    }
    then(onFulfilled, onRejected) {
    	if (this.status === RESOLVED) {
    		onFulfilled(this.value);
    	}
    	if (this.status === onRejected) {
    		onRejected(this.reason);
    	}
    }
}
const promise = new Promise((resolve, reject) => {
    console.log('executor');
    resolve('resolved');
    // throw new Error('失败');
    // reject('rejected');
});
promise.then(
    res => {
    	console.log(res);
    },
    err => {
    	console.log(err);
    }
);

优化then方法

如果我在生成Promise实例的时候,放了一段异步代码,异步代码执行完后才resolve,那么此时调用then方法的时候,promise的状态时pending,成功和失败也不会执行

const promise = new Promise((resolve, reject) => {
    setTimeout(()=> {
        resolve('resolve')
    }, 1000)
});

promise.then() //此时调用then方法,status的状态是pending,要在1秒以后才能拿到resolve状态

为解决上面的问题,我们需要在status为pending的时候,把传入的回调函数存入队列里面。当调用then方法的时候,在resolve或者reject函数中依次执行队列的函数。相当于发布订阅模式,先订阅,再发布。

const PENDING = 'pending'; // 等待态
const RESOLVED = 'resolved'; // 成功态
const REJECTED = 'rejected'; // 失败态

class MyPromise {
    constructor(executor) {
        this.status = PENDING; // 默认状态
        this.value = undefined; // 成功返回值
        this.reason = undefined; // 失败返回值
        this.onResolvedCallbacks = []; // 存放成功回调
        this.onRejectedCallbacks = []; // 存放失败回调
        const resolve = value => {
            if (this.status === PENDING) {
                this.status = RESOLVED;
                this.value = value;
                this.onResolvedCallbacks.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 (err) {
            reject(err);
        }
    }
    then(onFulfilled, onRejected) {
        if (this.status === RESOLVED) {
            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);
            });
        }
    }
}
const promise = new MyPromise((resolve, reject) => {
    console.log('executor');
    setTimeout(() => {
    	resolve('resolved');
    	// reject('rejected');
    }, 1000);
});
promise.then(
    res => {
    	console.log('success1', res);
    },
    err => {
    	console.log('failed1', err);
    }
);
promise.then(
    res => {
    	console.log('success2', res);
    },
    err => {
    	console.log('failed1', err);
    }
);

Promise中的链式调用

  1. Promise成功和失败的回调的返回值,可以传递到下一层的then;
  2. 如果返回的是普通值(非Promise和出错的情况),无论是失败的普通值,还是成功的普通值,都会传递到下一层的then的成功的回调中;
  3. 如果返回的是个Promise对象,会根据promise的状态决定传递到下一层then的成功或者失败回调;
  4. 如果then的回调函数中不写返回值,会默认返回undefined,undefined是普通值,会被传递到下一个then的成功的回调中;
  5. 如果离自己最近的then中没有错误处理,会向下查找,所以我们不需要写很多错误的回调。写最后一个then的错误的回调就好,如果所有then不传递错误的回调,那么会报错;
  6. 每次执行Promise.then的时候都会返回一个全新的promise,所以可以无限then下去,这点有区别于jQuery的链式调用。如果Promise返回的是一个this,因为之前的promise的状态是不能变更的,如果前一个then是成功,后一个then是失败,这样是不符合规范的,所以必须返回一个全新的Promise。
ajax('xxx.url')
    .then(
        res=> {
            return 200;
            // Promise.resolve('成功');
        },
        err=> {
            return 404; // 这种情况会传递到下一层then的成功的回调
            // throw new Error('错误') // 这种情况会传递到下一层then的失败的回调
        }
    )
    .then(
        res=> {
            console.log(res);
        },
        err=> {
            console.log(err);
        }
    )
const PENDING = 'pending'; // 等待态
const RESOLVED = 'resolved'; // 成功态
const REJECTED = 'rejected'; // 失败态
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        // 1.循环引用 自己等待自己完成
        // 可能会有promise2.then(()=> {return promise2}),这种自己等自己的情况
        // 抛出一个类型错误,结束掉Promise
        reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
    }
    // resolvePromise要兼容所有的库,因为还有别人写好的Promise,为了保证和别人的代码和别的库一起使用
    // 下面要做更严谨的判断
    
    // called变量用来标识promise的状态(别的库可能会变更我们的promise状态)
    // 如果promise是成功态,将不允许再走失败态,反之亦然。
    // 如果返回的是个普通值,不用添加标识
    let called;
    if ((typeof x === 'object' && x != null) || typeof x === 'function') {
        // 有可能是一个promise
        try {
            const then = x.then;
            // console.log('*****', x, then);
            // Promise {status: '', value:'', ...} [Function: then]
            if (typeof then === 'function') {
                // 如果x.then是一个函数,就认为x是一个promise,promise才有then方法
                
                // 执行then方法
                then.call(
                    // 根据promise的状态决定是成功还是失败
                    x,
                    y => {
                    if (called) return;
                        called = true;
                        // 成功
                        // resolve(y);
                        
                        // 因为y还可能是个promise,所以我们要递归调用,直到y变为普通值
                        resolvePromise(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
        resolve(x);
    }
}
class Promise {
    constructor(executor) {
    	this.status = PENDING; // 默认状态
    	this.value = undefined; // 成功返回值
    	this.reason = undefined; // 失败返回值
    	this.onResolvedCallbacks = []; // 存放成功回调
    	this.onRejectedCallbacks = []; // 存放失败回调
    	const resolve = value => {
    	    if (value instanceof Promise) {
    	        // 如果value是个Promise,递归解析,直到value为普通值
    	        value.then(resolve, reject);
    	    }
            if (this.status === PENDING) {
            	this.status = RESOLVED;
            	this.value = value;
            	this.onResolvedCallbacks.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 (err) {
    		reject(err);
    	}
    }
    then(onFulfilled, onRejected) {
        const promise2 = new Promise((resolve, reject) => {
            if (this.status === RESOLVED) {
                setTimeout(() => {
                    // 放置定时器,是因为在promise2这个实例没有new完之前是undefined
                    try {
                        // promise的executor使用了try...catch...,这里又使用一次是因为定时器里面是异步代码
                        // 不能捕获错误,所以追加一个try...catch...
                        const x = onFulfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            }
            if (this.status === REJECTED) {
                try {
                    const x = onRejected(this.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            }
            if (this.status === PENDING) {
                this.onResolvedCallbacks.push(() => {
                    // todo...
                    try {
                        const x = onFulfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
                this.onRejectedCallbacks.push(() => {
                    // todo...
                    try {
                        const x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            }
        });
        return promise2;
    }
}
const promise = new Promise((resolve, reject) => {
    resolve('resolved');
});
const promise2 = promise.then(data => {
    console.log(data); // resolved
    return new Promise((resolve, reject) => {
    	// resolve('yes');
    	reject('no');
    });
});
promise2.then(
    y => {
    	console.log('success', y);
    },
    e => {
    	console.log('failed', e);
    }
);

Promise的透传以及我们then方法的进一步优化

在ES6 Promise中调用then方法的时候,resolve或者reject的值,是可以跟随着then方法往下传递的,比如:

const p = new Promise((resolve, reject)=> {
    resolve(100);
    // reject(200);
})
p.then().then().then(
    res=> {
        console.log(res);
    },
    err= {
        consle.log(err);
    }
)
// 相当于
p
.then(res=> {
    return res;
})
.then(res=> {
    return res;
})
.then(res=> {
    console.log(res);
})
// 或者
p
.then(null, err=> {
    throw err;
})
.then(null, err=> {
    throw err;
})
.then(
    res=> {},
    err=> {
        console.log(err);
    }
)

所以,我们需要对promise的then方法中的回调函数追加一层判断

// ...
then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => throw err;
    // ...
}

至此,我们就根据Promise A+规范实现了一个Promise

Promise的测试

Promise A+规范只规范了then方法的实现,还有catch方法,race方法,all方法,finally方法不属于A+规范,我将会在下一篇文章中,实现这些方法。

首先,安装测试包

npm install promises-aplus-tests

其次,根据测试包的说明,我们需要给Promise添加一个方法,方便测试

// ... 上面Promise代码
Promise.defer = Promise.deferred = function() {
    const dfd = {};
    dfd.promise = new Promise((resolve, reject)=> {
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd;
}

module.export = Promise;

最后,测试我们的Promise代码

// 假如你的Promise文件名为promise.js
// 切换文件所在目录,执行
promises-aplus-tests promise.js
// 静静的等待结果就好了

Promise测试结果