最近看了阮一峰ES6入门中Promise部分的内容,Promise一直是学习以及面试中的重点,这里主要记录Promise的原生写法,以及使用Promise的一些注意事项,关于Promise基础部分的内容都可以在红宝书以及阮一峰的博客上面找到。
构造函数
function MyPromise(executor) {
this.PromiseState = "pending";
this.PromiseResult = undefined;
this.callbackList = [];
let resolve = (value) => {
if (this.PromiseState !== "pending") return
this.PromiseState = "fulfilled";
this.PromiseResult = value;
for (let callback of this.callbackList) {
callback.onResolved(value);
}
};
let reject = (reason) => {
if (this.PromiseState !== "pending") return
this.PromiseState = "rejected";
this.PromiseResult = reason;
for (let callback of this.callbackList) {
callback.onRejected(reason);
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
注意点:
Promise对象的状态一经改变,就不会再变了。因此在回调函数resolve和reject中应该加一句代码来判断当前Promise对象的状态是否已经改变,改变了的话就不能再改变它的状态了。在下面的例子中说明,Promise状态由pending转变为fulfilled,之后执行到reject(222)时,不会再改变状态了。
let p = new Promise((resolve, reject) => {
resolve(111);
reject(222)
})
// [[PromiseState]]: "fulfilled"
// [[PromiseResult]]: 111
console.log(p);
- 构造函数传入一个
executor函数,这个函数有两个参数:resolve和reject,函数传入就会执行。 - 如果在
executor函数中抛出错误,也会触发reject函数。 最后一个重点是,在executor函数中,Promise的状态改变如果依赖于一个异步操作,例如setTimeout,只有在setTimeout的延时时间到了以后才会调用resolve或reject来改变Promise的状态。因此要将这两个回调函数保存起来,等将来延时时间到了以后就可以正常触发回调函数,考虑到这种情况,可以直接设置一个数组来接收这些回调函数。
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(111)
}, 1000)
})
console.log(p);
then方法
MyPromise.prototype.then = function (onResolved, onRejected) {
return new MyPromise((resolve, reject) => {
let handleCallback = (callback) => {
let res = undefined;
// 判断then方法的回调是函数还是空值
// 这里的逻辑可以简化为:
// 如果回调是函数,那么res就是回调return的结果,
// 没有返回结果此时返回的Promise实例的PromiseResult为undefined
// 如果回调函数没得参数,那么此时then方法返回的Promise对象的
// PromiseResult等于当前对象的PromiseResult
// 解决穿透问题
if (typeof callback === "function") {
res = callback(this.PromiseResult);
} else {
res = this.PromiseResult;
}
try {
if (res instanceof Promise) {
res.then((value) => {
resolve(value)
}, (err) => {
reject(err);
})
} else {
// 这段代码有一个作用就是在finally函数中,除了在finally函数的回调函数中
// 抛出错误或返回状态为rejected的Promise,其它情况下finally返回的Promise对象的状态
// 都依赖于当前Promise的状态。因此,在then方法中除了判断当前Promise对象返回的值
// 是什么类型,还要依据当前Promise对象状态来决定finally回调函数返回的Promise的状态
if (this.PromiseState === "fulfilled") {
resolve(res);
}
if (this.PromiseState === "rejected") {
reject(res);
}
}
} catch (err) {
reject(res);
}
}
if (this.PromiseState === "fulfilled") {
console.log("fulfilled")
handleCallback(onResolved);
}
if (this.PromiseState === "rejected") {
console.log("rejected")
handleCallback(onRejected);
}
if (this.PromiseState === "pending") {
this.callbackList.push({
onResolved: () => {
handleCallback(onResolved);
},
onRejected: () => {
handleCallback(onRejected);
}
})
}
})
}
测试用例:
let p = new MyPromise((resolve, reject) => {
resolve(122344)
}).then().then(console.log).then((val) => {
// undefined
console.log(val)
return 89
})
// PromiseResult: 89
// PromiseState: "fulfilled"
console.log(p);
let p1 = new MyPromise((resolve, reject) => {
resolve(122344)
}).then().then().then((val) => {
// 122344
console.log(val)
})
// PromiseResult: undefined
// PromiseState: "fulfilled"
console.log(p1);
注意点:
then()方法接收两个回调函数作为参数,只有在Promise状态改变时才会触发这两个函数。- 在执行
Promise的同步代码时,可能会返回一个Promise对象,要通过then方法先把这个Promise实例的状态及返回值确定下来。
catch方法
// catch()方法可以直接用then()方法来实现
MyPromise.prototype.catch = function (onRejected) {
return this.then(null, onRejected);
}
Promise.resolve()
MyPromise.resolve = function (val) {
return new MyPromise((resolve, reject) => {
// Promise不能递归调用
if (this === val) reject("Chaining cycle detected for promise #<Promise>");
if (val instanceof MyPromise) {
val.then((value) => {
resolve(value)
}, (err) => {
reject(err)
})
} else {
resolve(val);
}
})
}
Promise.reject()
// Promise.reject()的参数会原封不动的返回
MyPromise.reject = function (val) {
return new MyPromise((resolve, reject) => {
reject(val);
})
}
Promise.finally()
// finally返回的Promise的状态除了返回rejected状态的Promise或抛出异常
// 其它情况返回的Promise的状态都跟当前Promise的状态一样
// 因此自己编写Promise时,要优先考虑当前Promise的状态
MyPromise.prototype.finally = function (callback) {
return this.then(
(value) => {
console.log(value);
// finally方法可能会返回Promise对象
// 因此要把这个Promise实例的状态以及返回值确定下来
// 只要出现问题,都会触发then方法中的reject函数
// 回调函数返回一个异常,新的Promise对象的状态也和当前Promise对象一致
MyPromise.resolve(callback()).then(null, (err) => {
return err
})
// 返回和当前Promise对象一致的PromiseResult
return value
},
(err) => {
console.log(err);
MyPromise.reject(callback()).then(null, (err) => {
return err
})
return err
}
)
}
Promise.all()
MyPromise.all = function (promiseList) {
let res = [];
return new MyPromise((resolve, reject) => {
for (let i = 0; i < promiseList.length; i++) {
promiseList[i].then((value) => {
res[i] = value;
if (res.length === promiseList.length) {
resolve(res);
}
}, (err) => {
reject(err);
})
}
})
}
Promise.race()
MyPromise.race = function (promiseList) {
return new MyPromise((resolve, reject) => {
for (let i = 0; i < promiseList.length; i++) {
promiseList[i].then((value) => {
resolve(value)
}, (err) => {
reject(err);
})
}
})
}
Promise.allSettled()
MyPromise.allSettled = function (promiseList) {
let res = [];
return new MyPromise((resolve, reject) => {
for (let i = 0; i < promiseList.length; i++) {
promiseList[i].then((value) => {
res[i] = value;
resolve(value)
}, (err) => {
res[i] = err;
reject(err);
})
}
if (res.length === promiseList.length) {
resolve(res);
}
})
}
Promise.any()
MyPromise.any = function (promiseList) {
let res = [];
return new MyPromise((resolve, reject) => {
for (let i = 0; i < promiseList.length; i++) {
promiseList[i].then((value) => {
resolve(value)
}, (err) => {
res[i] = err;
if (res.length === promiseList.length) {
reject(res);
}
})
}
})
}
你确定你现在弄明白Promise了吗?来看个例题
Promise.resolve().then(() => {
console.log(0);
return Promise.resolve(4);
}).then((res) => {
console.log(res)
})
Promise.resolve().then(() => {
console.log(1);
}).then(() => {
console.log(2);
}).then(() => {
console.log(3);
}).then(() => {
console.log(5);
}).then(() => {
console.log(6);
})
在看这道题之前我们需要具备这些知识:
Promise中只有涉及到状态变更后才需要被执行的回调函数才算是微任务,比如then、catch、finally。在使用Promise.resolve()时,这不是一个微任务,它只是定义了一个Promise实例,并改变其状态。
现在来公布答案~~~
打印结果:0123456
这道题是在我手写Promise时在看的一篇文章里面的题,文章也做了解释。
先看一个实例:
let p = new Promise((resolve, reject) => {
return p
})
// Uncaught (in promise) ReferenceError: Cannot access 'p' before initialization
从这里可以看出,必须要等到Promise初始化完成后,才能进行下一步,因此,在这里就需要创建一个异步任务去等待p的初始化完成。其实可以这样理解,当我们在初始化一个Promise实例时,肯定要让它初始化完成撒~
接下来解释一下这道题的打印过程:
- 一开始有两个
resolve,resolve是一个同步操作,因此它们分别执行then()方法; - 第一个
resolve打印0,并返回一个resolve状态的Promise,第二个resolve打印1; - 刚才我们说了,返回一个
Promise时,会添加一个微任务,此时就会执行第二个resolve的第二个then方法,打印出2; - 现在再来看
Promise.resolve(4),在then方法中返回一个Promise对象时,这是就会调用这个Promise的then方法,可以看上面then()方法中是怎么写的,then方法执行完回调函数返回一个Promise就会调用其then方法,将参数传递出去;接下来,第二个Promise也会执行它的下一个它then方法,打印出3; - 接下来又来到一个
resolve,这时Promise.resolve(4)已经将返回结果作为参数传递给下一个then方法,打印出4; - 最后,第二个
Promise分别打印出5和6.关于这里在then方法中返回Promise.resolve()的理解,可以直接简化成一条规律:只要是返回一个具有状态的Promise,它都会延迟两个then执行。
再来两个例题帮助加深理解Promise
例一
Promise.resolve()
.then(() => {
console.log("then1");
Promise.resolve().then(() => {
console.log("then1-1");
});
})
.then(() => {
console.log("then2");
});
打印结果:then1 → then1-1 → then2
在链式调用时,只有前一个then方法的回调函数执行完,下一个then方法的回调才能被加入到微任务队列中。
例二
let p = Promise.resolve();
p.then(() => {
console.log("then1");
Promise.resolve().then(() => {
console.log("then1-1");
});
}).then(() => {
console.log("then1-2");
});
p.then(() => {
console.log("then2");
});
打印结果:then1 → then2 → then1-1 → then1-2
同一个Promise的每一个链式调用的开端会依次首先进入微任务队列。
把上面的例子换个写法:
let p = Promise.resolve().then(() => {
console.log("then1");
Promise.resolve().then(() => {
console.log("then1-1");
});
}).then(() => {
console.log("then2");
});
p.then(() => {
console.log("then3");
});
打印结果:then1 → then1-1 → then2 → then1-3
p.then这里的p已经不再是最开始定义的那个,而是调用最后一个then生成的。
最后
这篇文章主要是自己在学习Promise以后总结下来的,文章里面的例子主要是借鉴其它作者的,下面必须要贴一下文章来源
下面是我对Promise的总结,写在Processon上面的,贴下网址 www.processon.com/outline/603…
山高路远,道阻且长,文章会不定时更新~学无止境,欢迎大家一起交流学习#