Promise是ES6中新推出的语法,用来解决异步回调而带来的回调地狱问题,能够解决回调地狱的关键就是Promise的实例方法then可以链式调用,也就是说实例方法then的返回值还是一个Promise实例对象。本篇文章将Promise拆分成不同的小模块,以递进的方式通过实现每一个小模块到最后整合实现一个自定义封装的Promise,目的是能够了解Promise的基本原理,强化学习。所以如果大家对本文有什么意见或者问题,可以在评论区留言,我会虚心学习本积极改正。
1.搭建初始化结构
首先根据用法搭建初始化结构,我们知道一个Promise对象实例有两个内置属性PromiseStatus :取值分别为fulfilled:(成功状态) rejected:(失败状态) pending:(等待状态);状态改变过程: pending -> fulfilled,pending -> rejected,且只能单向改变。
PromiseResult: 执行两个状态函数时传递的参数
用法:
let p = new Promise((resolve,reject) => {
resolve('success');
// 或者 reject('error)
})
封装的Promise.js文件内容
class Promise{
// 构造函数接受一个方法,需要注意的是这个方法是同步调用的
constructor(excutor) {
this.PromiseStatus = 'pending' // 初始化的状态为pending
this.PromiseResult = '' // 初始化结果
const resolve = (data) => {
}
const reject = (data) => {
}
// 因此在这里直接执行这个回调
excutor(resolve,reject)
}
}
2.完成resolve函数和reject函数
我们知道除了resolve和reject两个函数可以改变状态之外,如果抛出错误,Promise的状态会自动变为rejected状态。而且状态只能改变一次且是单向的,所以在改变状态的两个函数中需要拦截一下。
到这里一个简易版时Promise就完成了,下面在完成then方法的过程中继续不断的完善即可。
class Promise{
constructor(excutor) {
this.PromiseStatus = 'pending'
this.PromiseResult = ''
const resolve = (data) => {
if (this.PromiseStatus !== 'pending') {
return
}
// 这里有个问题需要注意一下,如果使用原生的js定义函数,这里的this会丢失,
// 指向window 可以在函数外部先保存一下
this.PromiseStatus = 'fulfilled'
this.PromiseResult = data
}
const reject = (data) => {
if (this.PromiseStatus !== 'pending') {
return
}
this.PromiseStatus = 'rejected'
this.PromiseResult = data
}
// 在这里捕获错误,将promise的状态变成rejected状态
try{
excutor(resolve,reject)
}catch(error){
reject(error)
}
}
}
3.then方法的结构
then接收两个处理状态的回调函数作为参数,在Promise状态发生变化时执行相应的回调,这里有两个点需要注意:改变Promise状态的函数可以同步调用也可以异步调用,同步调用当前代码不会出现什么问题,因为到then方法执行时Promise的状态已经改变了,但是当异步调用时代码执行到then方法Promise的状态还是pending状态,then方法中的回调不会执行,即使1秒之后状态被改变了也不会执行。
因此需要单独处理这种异步改变Promise状态的情况,我们知道当Promise状态发生改变时then方法的两个回调才会根据状态选择执行。所以在代码执行到then方式发现Promise的状态还是pending状态时,就将这两个回调先提交,等到状态被更新之后再执行回调(这里就像Vue中的观察者模式)。
用法:
let p = new Promise((resolve,reject) => {
// 1.同步调用
resolve('success')
// 2.异步调用
setTimeOut(resolve('success'),1000)
})
p.then(v => {
console.log(v)
},r => {
console.log(r)
})
Promise.js文件
class Promise{
constructor(excutor) {
this.PromiseStatus = 'pending'
this.PromiseResult = ''
this.callback = {} // 用来保存then方法提交的回调
const resolve = (data) => {
if (this.PromiseStatus !== 'pending') {
return
}
// 这里有个问题需要注意一下,如果使用原生的js定义函数,这里的this会丢失,
// 指向window 可以在函数外部先保存一下
this.PromiseStatus = 'fulfilled'
this.PromiseResult = data
// Promise的状态变成fulfilled状态时,检查回调队列
if (this.callback.onResolve) {
this.callback.onResolve(this.PromiseResult)
}
}
const reject = (data) => {
if (this.PromiseStatus !== 'pending') {
return
}
this.PromiseStatus = 'rejected'
this.PromiseResult = data
// Promise的状态变成rejected状态时,检查回调队列
if (this.callback.onReject) {
this.callback.onReject(this.PromiseResult)
}
}
// 在这里捕获错误,将promise的状态变成rejected状态
try{
excutor(resolve,reject)
}catch(error){
reject(error)
}
}
then(onResolve,onReject){
if(this.PromiseStatus === 'fulfilled'){
onResolve(this.PromiseResult)
}
if(this.PromiseStatus === 'rejected'){
onReject(this.PromiseResult)
}
// pending状态时提交两个回调
if (this.PromiseStatus === 'pending') {
this.callback = {
onResolve,
onReject
}
}
}
}
4. 解决一个Promise实例对象绑定多个then的情况
和第三步一样同步不受影响,但是异步会出问题,因为在then方法中只提交了一次回调函数的注册,所以后一次then()方法注册的两个回调函数会覆盖调前面的。
解决方案 : 将对象换成数组,每注册一个就放进去一个,当Promise状态发生改变时按照顺序遍历执行即可。
使用方法:
let p = new Promise((resolve,reject) => {
// reject('error')
// resolve('success')
// throw 'errors'
setTimeout(() => {
resolve('success')
}, 1000);
})
p.then(v => {
console.log(v);
},r => {
console.log(r);
})
p.then(v => {
alert('after');
},r => {
console.log(r);
})
Promise.js文件内容
class Promise{
constructor(excutor) {
this.PromiseStatus = 'pending'
this.PromiseResult = ''
this.callbacks = [] // 用来保存then方法提交的回调
const resolve = (data) => {
if (this.PromiseStatus !== 'pending') {
return
}
// 这里有个问题需要注意一下,如果使用原生的js定义函数,这里的this会丢失,
// 指向window 可以在函数外部先保存一下
this.PromiseStatus = 'fulfilled'
this.PromiseResult = data
// Promise的状态变成fulfilled状态时,检查回调队列然后遍历执行
this.callbacks.forEach(callback => {
callback.onResolve(this.PromiseResult)
})
}
const reject = (data) => {
if (this.PromiseStatus !== 'pending') {
return
}
this.PromiseStatus = 'rejected'
this.PromiseResult = data
// Promise的状态变成rejected状态时,检查回调队列然后遍历执行
this.callbacks.forEach(callback => {
callback.onReject(this.PromiseResult)
})
}
// 在这里捕获错误,将promise的状态变成rejected状态
try{
excutor(resolve,reject)
}catch(error){
reject(error)
}
}
then(onResolve,onReject){
if(this.PromiseStatus === 'fulfilled'){
onResolve(this.PromiseResult)
}
if(this.PromiseStatus === 'rejected'){
onReject(this.PromiseResult)
}
// pending状态时向回调队列注册回调
if (this.PromiseStatus === 'pending') {
this.callbacks.push({
onResolve,
onReject
})
}
}
}
5.then方法返回值状态(重点)
Promise可以链式调用的核心就在这里,因为then方法的返回值是一个promise实例对象。
then方法返回的Promise实例对象状态和结果分为以下几种情况:(为了区分称呼将then方法返回的Promise对象设置为thenPromise)
1.当时返回值是一个非Promise对象类型的值时。此时
thenPromise对象的状态为成功,值为当前返回的这个值。
2.当返回值是一个Promise对象时,thenPromise对象的状态为返回的这个Promise的状态,值是当前这个Promise的改变状态函数传递的参数。
3.抛出错误时,thenPromise的状态为失败,值为抛出的错误信息。
情况还是分为两种:同步修改和异步修改
5.1同步修改状态
let p = new Promise((resolve,reject) => {
// reject('error')
resolve('success')
// throw 'errors'
})
const res = p.then(v => {
console.log(v);
throw '111'
},r => {
console.log(r);
})
console.log(res); //测试代码 输出thenPromise 看结果
此时需要修改then方法中的代码,前面的几步都以Promise构造函数为主,从这步开始重心才移动到then方法上。首先得先返回一个Promise对象,根据上面罗列出来的三种情况来改变thenPromise的状态即可。
class Promise{
constructor(excutor) {
this.PromiseStatus = 'pending'
this.PromiseResult = ''
this.callbacks = [] // 用来保存then方法提交的回调
const resolve = (data) => {
if (this.PromiseStatus !== 'pending') {
return
}
// 这里有个问题需要注意一下,如果使用原生的js定义函数,这里的this会丢失,
// 指向window 可以在函数外部先保存一下
this.PromiseStatus = 'fulfilled'
this.PromiseResult = data
// Promise的状态变成fulfilled状态时,检查回调队列然后遍历执行
this.callbacks.forEach(callback => {
callback.onResolve(this.PromiseResult)
})
}
const reject = (data) => {
if (this.PromiseStatus !== 'pending') {
return
}
this.PromiseStatus = 'rejected'
this.PromiseResult = data
// Promise的状态变成rejected状态时,检查回调队列然后遍历执行
this.callbacks.forEach(callback => {
callback.onReject(this.PromiseResult)
})
}
// 在这里捕获错误,将promise的状态变成rejected状态
try{
excutor(resolve,reject)
}catch(error){
reject(error)
}
}
then(onResolve,onReject){
return new Promise((resolve, reject) => {
if(this.PromiseStatus === 'fulfilled'){
try {
let result = onResolve(this.PromiseResult)
if (result instanceof Promise) { // 判断返回值是不是Promise对象
result.then(v => { // 如果是直接调用它自己的then方法
resolve(v)
},r => {
reject(r)
})
}else{ // 如果不是Promise对象则调用resolve方法改变状态
resolve(result)
}
} catch (error) { // 最后捕获抛错导致的状态改变
reject(error)
}
}
if(this.PromiseStatus === 'rejected'){
try {
let result = onReject(this.PromiseResult) // 这里和上面一样 都是同步的情况
if (result instanceof Promise) {
result.then(v => {
resolve(v)
},r => {
reject(r)
})
}else{
resolve(result)
}
} catch (error) {
reject(error)
}
}
// pending状态时向回调队列注册回调
if (this.PromiseStatus === 'pending') {
this.callbacks.push({
onResolve,
onReject
})
}
});
}
}
5.2异步修改状态
let p = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('after success')
}, 1000);
})
const res = p.then(v => {
console.log(v);
throw '111'
},r => {
console.log(r);
})
console.log(res); //测试代码 输出thenPromise 看结果
Promise.js
class Promise{
constructor(excutor) {
this.PromiseStatus = 'pending'
this.PromiseResult = ''
this.callbacks = [] // 用来保存then方法提交的回调
const resolve = (data) => {
if (this.PromiseStatus !== 'pending') {
return
}
// 这里有个问题需要注意一下,如果使用原生的js定义函数,这里的this会丢失,
// 指向window 可以在函数外部先保存一下
this.PromiseStatus = 'fulfilled'
this.PromiseResult = data
// Promise的状态变成fulfilled状态时,检查回调队列然后遍历执行
this.callbacks.forEach(callback => {
callback.onResolve(this.PromiseResult)
})
}
const reject = (data) => {
if (this.PromiseStatus !== 'pending') {
return
}
this.PromiseStatus = 'rejected'
this.PromiseResult = data
// Promise的状态变成rejected状态时,检查回调队列然后遍历执行
this.callbacks.forEach(callback => {
callback.onReject(this.PromiseResult)
})
}
// 在这里捕获错误,将promise的状态变成rejected状态
try{
excutor(resolve,reject)
}catch(error){
reject(error)
}
}
then(onResolve,onReject){
let self = this
return new Promise((resolve, reject) => {
if(this.PromiseStatus === 'fulfilled'){
try {
let result = onResolve(this.PromiseResult)
if (result instanceof Promise) { // 判断返回值是不是Promise对象
result.then(v => { // 如果是直接调用它自己的then方法
resolve(v)
},r => {
reject(r)
})
}else{ // 如果不是Promise对象则调用resolve方法改变状态
resolve(result)
}
} catch (error) { // 最后捕获抛错导致的状态改变
reject(error)
}
}
if(this.PromiseStatus === 'rejected'){
try {
let result = onReject(this.PromiseResult) // 这里和上面一样 都是同步的情况
if (result instanceof Promise) {
result.then(v => {
resolve(v)
},r => {
reject(r)
})
}else{
resolve(result)
}
} catch (error) {
reject(error)
}
}
// pending状态时向回调队列注册回调
if (this.PromiseStatus === 'pending') {
this.callbacks.push({
onResolve(){
try {
let result = onResolve(self.PromiseResult)
if (result instanceof Promise) { // 判断返回值是不是Promise对象
result.then(v => { // 如果是直接调用它自己的then方法
resolve(v)
},r => {
reject(r)
})
}else{ // 如果不是Promise对象则调用resolve方法改变状态
resolve(result)
}
} catch (error) { // 最后捕获抛错导致的状态改变
reject(error)
}
},
onReject(){
try {
let result = onReject(self.PromiseResult)
if (result instanceof Promise) {
result.then(v => {
resolve(v)
},r => {
reject(r)
})
}else{
resolve(result)
}
} catch (error) {
reject(error)
}
}
}
});
}
}
完成这两步之后会发现一个优化点,就是改变then方法返回状态的代码重复,所以将其抽离出来定义为callback函数,这一步完成之后then函数基本就实现了。
class Promise{
constructor(excutor) {
this.PromiseStatus = 'pending'
this.PromiseResult = ''
this.callbacks = [] // 用来保存then方法提交的回调
const resolve = (data) => {
if (this.PromiseStatus !== 'pending') {
return
}
// 这里有个问题需要注意一下,如果使用原生的js定义函数,这里的this会丢失,
// 指向window 可以在函数外部先保存一下
this.PromiseStatus = 'fulfilled'
this.PromiseResult = data
// Promise的状态变成fulfilled状态时,检查回调队列然后遍历执行
this.callbacks.forEach(callback => {
callback.onResolve(this.PromiseResult)
})
}
const reject = (data) => {
if (this.PromiseStatus !== 'pending') {
return
}
this.PromiseStatus = 'rejected'
this.PromiseResult = data
// Promise的状态变成rejected状态时,检查回调队列然后遍历执行
this.callbacks.forEach(callback => {
callback.onReject(this.PromiseResult)
})
}
// 在这里捕获错误,将promise的状态变成rejected状态
try{
excutor(resolve,reject)
}catch(error){
reject(error)
}
}
then(onResolve,onReject){
let self = this
return new Promise((resolve, reject) => {
let callback = type => {
try {
let result = type(self.PromiseResult) // 为了方便起见统一用self
if (result instanceof Promise) { // 判断返回值是不是Promise对象
result.then(v => { // 如果是直接调用它自己的then方法
resolve(v)
},r => {
reject(r)
})
}else{ // 如果不是Promise对象则调用resolve方法改变状态
resolve(result)
}
} catch (error) { // 最后捕获抛错导致的状态改变
reject(error)
}
}
if(this.PromiseStatus === 'fulfilled'){
callback(onResolve)
}
if(this.PromiseStatus === 'rejected'){
callback(onReject)
}
// pending状态时向回调队列注册回调
if (this.PromiseStatus === 'pending') {
this.callbacks.push({
onResolve(){
callback(onResolve)
},
onReject(){
callback(onReject)
}
})
}
});
}
}
6.完成catch函数以及解决Promise的异常穿透
then方法实现之后catch方法就变的异常的简单,只需要将then方法的第一个参数设置为undefined即可
catch的实现代码:
catch(onReject){
this.then(undefined,onReject)
}
接下来就是catch异常穿透问题,首先什么是异常穿透,异常穿透:一个链式调用的then方法不需要给每个then方法都设置异常处理的回调,只需要在链式调用的结尾设置一个catch方法即可。也就是说如果第一个then方法返回的Promise状态是失败,这个失败的状态会一直穿透到离自己最近的catch函数,由catch函数的回调进行异常处理。
例如: 由此可见then链式调用时第一个出现失败状态的Promise会一直进行异常穿透直到找到离自己最近的catch方法。
let p = new Promise((resolve,reject) => {
resolve()
})
p.then(v => {
console.log(v);
throw 'error'
})
.catch(r => {
console.log(r,'====='); // error
})
.then(data => {
console.log('11111');
throw 'second error'
})
.then(data => {
resolve('success')
})
.catch(reason => {
console.warn(reason); // second error
})
实现异常处理:根据上述情况可以看出,异常处理的回调单独设置在catch函数里面,而then方法需要接受两个参数,此时第二个参数没有传递所以之前剥离的callback函数的type就是undefined,会报错。
let callback = type => {
try {
let result = type(self.PromiseResult) // 为了方便起见统一用self
if (result instanceof Promise) { // 判断返回值是不是Promise对象
result.then(v => { // 如果是直接调用它自己的then方法
resolve(v)
},r => {
reject(r)
})
}else{ // 如果不是Promise对象则调用resolve方法改变状态
resolve(result)
}
} catch (error) { // 最后捕获抛错导致的状态改变
reject(error)
}
}
因此实现异常穿透的关键还是在then方法里面,没有传递我们就需要在then方法里面自定义一个,在关注异常穿透的同时,根据Promise的用法then方法可以两个回调都不进行传递,所以在这里一起处理。
then方法新加的代码其他地方不变;
then(onResolve,onReject){
let self = this
return new Promise((resolve, reject) => {
// 发现onReject回调函数没有传递就自定义一个。并且将前一个then方法执行返回的失败的Promise一直向下抛,直到遇见可以处理失败状态的catch函数
if (typeof onReject !== 'function') {
onReject = reason => {
throw reason
}
}
// onResolve也是一样将结果一直向下传递,直到遇到处理的回调函数
if (typeof onResolve !== 'function') {
onResolve = value => value
}
}
7.resolve和reject函数的实现
这两个是Promise对象自身的属性(用static关键字修饰),简写的方式创建Promise实例对象。
static resolve(value){
return new Promise((resolve, reject) => {
resolve(value)
});
}
static reject(reason){
return new Promise((resolve, reject) => {
reject(reason)
});
}
8.Promise.all的实现
也是Promise自身的属性,该方法接收一个由多个promise实例对象组成的数组,如果这些实例promise的状态全是成功状态的话返回值是每个对象执行成功后返回的值组成的数组作为all方法返回的对象的PromiseResult的值,而PromiseStatus的值则为fulfilled,但是只要有一个出现失败的状态则PromiseStatus:'rejected',PromiseResult : '失败的那个promise执行reject函数的值'
static all(promises){
return new Promise((resolve, reject) => {
let count = 0
let res = []
for(let i = 0 ; i < promises.length; i ++){
promises[i].then(value => {
count ++
// 这里直接赋值的形式,将输入的顺序和返回的顺序一一对应,使用push方法如果遇到异步改变状态会导致顺序发生变化
res[i] = value // 使用块级作用域的特性实现一一对应
if (count === promises.length) {
resolve(res)
}
},reason => {
reject(reason)
})
}
});
}
9.Promise.race的实现
API的用法和all一样,区别就是的和自己字面意思一样竞争、比赛的意思。遇见数组中第一个promise实例对象,这个实例长啥样它返回的promise对象就是啥样。(一直没有遇见或者想到使用它的场景,有想法的或者用过麻烦您评论一下子,我学习学习,感谢)
static race(promises){
return new Promise((resolve, reject) => {
promises.forEach(promise => {
promise.then(value => {
resolve(value)
},reason => {
reject(reason)
})
})
});
}
10.模拟异步
每一步就像闯关一样,终于到最后一关了(🎉🎉🎉)。我们知道then方法是一个异步的方法,而我们之前写的全是同步,所以使用setTimeOut来模拟一下异步。
完结版Promise.js
class Promise{
constructor(excutor) {
this.PromiseStatus = 'pending'
this.PromiseResult = ''
this.callbacks = [] // 用来保存then方法提交的回调
const resolve = (data) => {
if (this.PromiseStatus !== 'pending') {
return
}
// 这里有个问题需要注意一下,如果使用原生的js定义函数,这里的this会丢失,
// 指向window 可以在函数外部先保存一下
this.PromiseStatus = 'fulfilled'
this.PromiseResult = data
// Promise的状态变成fulfilled状态时,检查回调队列然后遍历执行
setTimeout(() => {
this.callbacks.forEach(callback => {
callback.onResolve(this.PromiseResult)
})
}, 0);
}
const reject = (data) => {
if (this.PromiseStatus !== 'pending') {
return
}
this.PromiseStatus = 'rejected'
this.PromiseResult = data
// Promise的状态变成rejected状态时,检查回调队列然后遍历执行
setTimeout(() => {
this.callbacks.forEach(callback => {
callback.onReject(this.PromiseResult)
})
}, 0);
}
// 在这里捕获错误,将promise的状态变成rejected状态
try{
excutor(resolve,reject)
}catch(error){
reject(error)
}
}
then(onResolve,onReject){
let self = this
return new Promise((resolve, reject) => {
if (typeof onReject !== 'function') {
onReject = reason => {
throw reason
}
}
if (typeof onResolve !== 'function') {
onResolve = value => value
}
let callback = type => {
try {
let result = type(self.PromiseResult) // 为了方便起见统一用self
if (result instanceof Promise) { // 判断返回值是不是Promise对象
result.then(v => { // 如果是直接调用它自己的then方法
resolve(v)
},r => {
reject(r)
})
}else{ // 如果不是Promise对象则调用resolve方法改变状态
resolve(result)
}
} catch (error) { // 最后捕获抛错导致的状态改变
reject(error)
}
}
if(this.PromiseStatus === 'fulfilled'){
setTimeout(() => {
callback(onResolve)
},0);
}
if(this.PromiseStatus === 'rejected'){
setTimeout(() => {
callback(onReject)
}, 0);
}
// pending状态时向回调队列注册回调
if (this.PromiseStatus === 'pending') {
this.callbacks.push({
onResolve(){
callback(onResolve)
},
onReject(){
callback(onReject)
}
})
}
});
}
catch(onReject){
this.then(undefined,onReject)
}
static resolve(value){
return new Promise((resolve, reject) => {
resolve(value)
});
}
static reject(reason){
return new Promise((resolve, reject) => {
reject(reason)
});
}
static all(promises){
return new Promise((resolve, reject) => {
let count = 0
let res = []
for(let i = 0 ; i < promises.length; i ++){
promises[i].then(value => {
count ++
// 这里直接赋值的形式,将输入的顺序和返回的顺序一一对应,使用push方法如果遇到异步改变状态会导致顺序发生变化
res[i] = value // 使用块级作用域的特性实现一一对应
if (count === promises.length) {
resolve(res)
}
},reason => {
reject(reason)
})
}
});
}
static race(promises){
return new Promise((resolve, reject) => {
promises.forEach(promise => {
promise.then(value => {
resolve(value)
},reason => {
reject(reason)
})
})
});
}
}
全程按照自己掌握的Promise用法的思路来实现的,自身会有一些地方理解不太正确或者偏差,希望大家能够在评论区指点一下。不熟悉的同学可以参考一下,Promise玩的很溜的同学可以找找bug来加深一下自己的印象或者理解。谢谢大家!