Description
Promise的源码实现
在学习源码之前,建议您先通读并学习以下知识点
学习完以上手册后如果想继续加深学习,可以看这个精彩的讲解视频!
Promise和setTimeout的区别 ?
Promise 是微任务,setTimeout 是宏任务,同一个事件循环中,promise总是先于 setTimeout 执行。
Promise构造函数是同步还是异步执行,then中的方法呢 ?
Promise的构造函数是同步执行
的。then中的方法是异步执行
的。
promise 有几种状态, Promise 有什么优缺点 ?
promise有三种状态: fulfilled, rejected, pending.
Promise 的优点:
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果
- 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
Promise 的缺点:
- 无法取消 Promise
- 当处于pending状态时,无法得知目前进展到哪一个阶段
如果您已经学习完以上内容,那么Let‘s go!
简易版
const PENGING = 'PENGING',
FULFILLED = 'FULFILLED',
REJECTED = 'REGECTED';
function _Promise (executor){
this.status = PENGING;
this.value = undefined;
this.reason = undefined;
this.onFulFilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = function(value){
if(this.status == PENGING){
this.status = FULFILLED;
this.value = value;
this.onFulFilledCallbacks.forEach(fn => fn()) //发布
}
}
const reject = function(reason){
if(this.status == PENGING){
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn()) //发布
}
}
try { // 捕获 new Promise((resolve, reject) => { throw new Error("有异常")}) 这种情况
executor(resolve, reject)
} catch (error) {
reject(err);
}
}
_Promise.prototype.then = function(onFulFilled, onRejected){
if(this.status == FULFILLED){
onFulFilled(this.value);
}
if(this.status == REJECTED){
onRejected(this.reason);
}
// 1. 处理 new Promise((resolve, reject) => { setTimeOut(resolve, 0)}) 这种异步的情况
// 2. 处理多个 then 调用情况
if(this.status == PENGING){
// 订阅
this.onFulFilledCallbacks.push(() => {
onFulFilled(this.value);
})
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
})
}
}
完全版
/**
* 1. new Promise时,需要传递一个 executor 执行器,执行器立刻执行
* 2. executor 接受resolve 和 reject两个参数
* 3. promise 只能从 pending 到 rejected, 或者从 pending 到 fulfilled
* 5. 调用 then 时,如果是成功,则执行 onFulfilled。
* 如果失败,那么执行 onRejected。
* 如果promise的状态是pending,则需要使用 发布订阅
* 6. then 的参数 onFulfilled 和 onRejected 可以缺省
* 7. 多个promise嵌套时候执行最里面的promise的resolve或者reject
*/
const PENDING = 'PENDING',
FULFILLED = 'FULFILLED',
REJECTED = 'REJECTED';
function Promise(excutor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.resolveCallbacks = []; // 成功的回调集合,即发布
this.rejectCallbacks = []; // 失败的回调集合,即发布
let self = this;
const resolve = function (value) {
if (self.status === PENDING) {
self.status = FULFILLED;
self.value = value;
self.resolveCallbacks.forEach(fn => fn());
}
}
const reject = function (reason) {
if(self.status === PENDING){
self.status = REJECTED;
self.reason = reason;
self.rejectCallbacks.forEach(fn => fn());
}
}
try{ // 防止excutor语句有throw Error
excutor(resolve, reject);
}catch(e){
reject(e);
}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
// onFulfilled、onRejected没传值时候手动赋值,处理then().then().then().then((value)=>{},(reason)=>{})这种情况
onFulfilled = typeof onFulfilled === 'function'? onFulfilled: value => value;
// throw会被外层的catch捕获
onRejected = typeof onRejected === 'function'? onRejected: reason => {throw reason};
let self = this;
let promise2 = new Promise((resolve, reject) => {
if(self.status === FULFILLED){
// 通过使用宏任务setTimeout使内部的resolvePromise函数可以使用promise2,
// 否则promise2内部无法使用自己
setTimeout(() => {
try {
let x = onFulfilled(self.value);
// 判断x是promise类型还是常量
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if(self.status === REJECTED){
setTimeout(() => {
try {
let x = onRejected(self.reason);
// 判断x是promise类型还是常量
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
// 该分支处理的是异步请求时候,即没有立即执行resolvePromise,所以异步请求时候可以正常使用promise2,所以不用setTimeout包resolvePromise函数
if(self.status === PENDING){
self.resolveCallbacks.push(() => { // 订阅
try {
let x = onFulfilled(self.value);
// 判断x是promise类型还是常量
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
self.rejectCallbacks.push(() => { // 订阅
try {
let x = onRejected(self.reason);
// 判断x是promise类型还是常量
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
}
})
return promise2;
}
function resolvePromise (promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('chaining cycle'));
}
// call作用:防止出现下面resolve重复调用的情况,重复情况下只执行第一个resolve(1)
// resolve(1);
// resolve(2);
let called = false;
if ((typeof x === 'object' && x != null) || typeof x === 'function') {
// let then = x.then可能报错
try {
let then = x.then;
// 判断x是promise类型还是常量
if (typeof then === 'function') {
then.call(x, (y) => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, (r) => {
if (called) return;
called = true;
reject(r);
})
} else {
if (called) return;
called = true;
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
}else{
resolve(x);
}
}
// catch 其实就是then的语法糖
// Promise中的then第二个参数和catch主要区别就是,如果在then的第一个函数里抛出了异常,后面的catch能捕获到,
// 而then的第二个函数捕获不到。
Promise.prototype.catch = function(errCallback){
this.then(null, errCallback)
}
module.exports = Promise;
then的第二个参数和catch捕获错误信息的时候会就近原则,如果是promise内部报错,reject抛出错误后,then的第二个参数和catch方法都存在的情况下,只有then的第二个参数能捕获到,如果then的第二个参数不存在,则catch方法会捕获到。
写完以上主要代码后,可以使用promises-aplus-tests
来测试所编写的代码是否符合PromiseA+的规范。
安装命令如下:
npm install -g promises-aplus-tests
如果我们的文件命名为test.js
的话,那请在对应目录下执行以下命令
promises-aplus-tests test.js
Promise的其他方法
Promise.resolve
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
1、如果 value 是 thenable 对象(具有then方法的对象),将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
2、如果 value 是 promise 对象,则直接返回这个 promise 对象。
3、其他情况,直接返回以该值为成功状态
的 promise 对象。
Promise.resolve = function (data) {
// 1 参数是一个 Promise 实例,不做任何修改、原封不动地返回这个实例
if (data instanceof Promise) {
return data
}
// 2 参数是一个thenable对象,将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
if (data.then) {
return new Promise((resolve, reject) => {
data.then(resolve, reject)
})
}
// 3 参数不是具有then方法的对象,或根本就不是对象
// 4 不带有任何参数
return new Promise((resolve) => {
resolve(data)
})
}
Promise.reject
Promise.reject()方法会原封不动地返回reject的参数。
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
}
任何一个await
语句后面的Promise
对象变为reject
状态,那么整个async
函数都会中断执行。如果不catch
,async
函数直接抛出reject
了,async
里在await
后面的代码是不会执行的。
一般写catch
有以下几种方法(推荐使用第一种方法):
- 1、将
await
代码写在try catch
代码块中, 这样当await
抛出异常时直接被catch
捕获,不会影响下面代码的运行。
async function f() {
try {
await Promise.reject('出错了');
} catch(err) {
console.log(err);
}
return await Promise.resolve('hello world');
}
f().then(v => console.log(v))
// 出错了
// hello world
- 2、另一种方法是
await
后面的Promise
对象再跟一个catch
方法,处理前面可能出现的错误。
async function f() {
await Promise.reject('出错了').catch(e => console.log(e));
return await Promise.resolve('hello world');
}
f().then(v => console.log(v))
// 出错了
// hello world
Promise.all
1、传入一个Iterable,但大部分是数组。
2、如果有一个promise失败,那么Promise.all返回失败。
3、在任何情况下,Promise.all 返回的完成状态都是一个数组。
Promise.all = function (promises){
const arr = Array.from(promises);
return new Promise((resolve, reject) => {
const len = arr.length;
const res = [];
let count = 0;
for(let i = 0; i < len; i++){
Promise.resolve(arr[i]).then(value => {
res[i] = value;
if(++count == len){
resolve(res);
}
}, reason => reject(reason));
}
})
}
Promise.all([2, 3, Promise.resolve(1)]).then(value => console.log(value)) // [2, 3, 1]
Promise.all([2, 3, Promise.reject(4)]).then(value => console.log(value)).catch(r => console.log(r)) // 4
Promise.race
Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
常用在开发程序与测速程序一起跑来判断开发程序的速度是否符合测速程序要求。
Promise.race = function(promises) {
return new Promise(function(resolve, reject) {
for (var i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then(function(value) {
resolve(value)
}, function(reason) {
reject(reason)
})
}
})
}
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p1')
}, 1000);
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p2')
}, 2000);
})
Promise.race([p1, p2]).then(data => {
console.log(data); // p1
})
Promise.finally
不管成功还是失败,都会走到finally中,并且finally之后,还可以继续then。并且会将值原封不动的传递给后面的then.
Promise.prototype.finally = function (callback) {
return this.then((value) => {
return Promise.resolve(callback()).then(() => {
return value;
});
}, (err) => {
return Promise.resolve(callback()).then(() => {
throw err;
});
});
}