[手写系列] 带你实现一个简单的Promise

从网站建设到定制行业解决方案,为提供网站制作、网站建设服务体系,各种行业企业客户提供网站建设解决方案,助力业务快速发展。创新互联建站将不断加快创新步伐,提供优质的建站服务。

简介

学习之前 需要先对Promise有个基本了解哦,这里都默认大家都是比较熟悉Promise的

本次将带小伙伴们实现Promise的基本功能

  1. Promise的基本骨架
  2. Promisethen
  3. Promise.then的多次调用
  4. then链式调用
  5. catch的实现
  6. finally的实现

01-搭建基本骨架

const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";

class ZXPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING;
        const resolve = (value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_FULFILLED;
                console.log(value);
            }
        }
        const rejected = (reason) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_REJECTED;
                console.log(reason);
            }
        }
        executor(resolve, rejected)
    }
}

// 初步搭建好Promise的construtor结构
const promise = new ZXPromise((resolve, rejected) => {
    resolve("123");
    rejected("wushichu")
})
  • 因为Promise有三种状态pending,fulfilled,rejected,我们这里就声明三个常量来代表这三种状态
  • Promise中需要传递一个回调函数,他的参数中包含了resolverejected,调用resolve之后,状态会变为fulfilled,调用rejected,状态会变成rejected
  • 我定义了一个类,我们在constructor中定义所需要的resolverejected函数,然后将这两个函数传入那个executor中去,这样Promise的基本骨架就已经搭建完成了,非常简单.

02-实现Promise的then功能

const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";

class ZXPromise {
	constructor(executor) {
		this.status = PROMISE_STATUS_PENDING;
		const resolve = (value) => {
			if (this.status === PROMISE_STATUS_PENDING) {
				queueMicrotask(() => {
          //因为只有pending状态才能进行变化
          if(this.status!==PROMISE_STATUS_PENDING) return
          this.status = PROMISE_STATUS_FULFILLED;
					if (this.onfufilled)
						this.onfufilled(value);
				})
			}
		}
		const rejected = (reason) => {
			if (this.status === PROMISE_STATUS_PENDING) {
				queueMicrotask(() => {
          if(this.status!==PROMISE_STATUS_PENDING) return
          this.status = PROMISE_STATUS_REJECTED;
					if (this.onrejected)
						this.onrejected(reason);
				})
			}
		}
		executor(resolve, rejected)
	}
	then(onfufilled, onrejected) {
		this.onfufilled = onfufilled;
		this.onrejected = onrejected;
	}
}

// 接下来开始写then方法
const promise = new ZXPromise((resolve, rejected) => {
	resolve("123");
	rejected("wushichu");
})

promise.then((res) => {
	console.log("res", res);
}, (err) => {
	console.log("err", err);
})
  • then方法中接受两个参数,分别是onfulfilledonrejected两个函数,分别对应着状态fulfilledrejected
  • 这里要注意一个点我在resolverejected中都使用了queueMicrotask,这里使用的目的是为了保证顺序执行的一致性,确保在then方法执行过后,再去执行相关代码,这里需要大家熟悉微任务队列和宏任务队列,推荐大家看下这篇文章
    在JS中使用queueMicroTask

03-Promise.then多次调用

大家可以用上一部分的代码实验一下,如果多次调用,会发现只有最后一个输出,并且在定时器中使用,会出现结果为undefined

p1.then((res) => {
	console.log("res1", res);
});

p1.then((res) => {
	console.log('res2: ', res);
});

setTimeout(() => {
	p1.then((res) => {
		console.log("res4", res);
	})
}, 1000);

现在我们来解决下上述问题,看代码

const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";

class ZXPromise {
	constructor(executor) {
		this.status = PROMISE_STATUS_PENDING;
		this.value = undefined;
		this.reason = undefined;
		this.onfufilled = [];
		this.onrejected = [];
		const resolve = (value) => {
			if (this.status === PROMISE_STATUS_PENDING) {
				queueMicrotask(() => {
					if (this.status !== PROMISE_STATUS_PENDING) return
					this.status = PROMISE_STATUS_FULFILLED;
					this.value = value;
					this.onfufilled.forEach(fn => {
						fn(value);
					});
				})
			}
		}
		const rejected = (reason) => {
			if (this.status === PROMISE_STATUS_PENDING) {
				queueMicrotask(() => {
					if (this.status !== PROMISE_STATUS_PENDING) return
					this.status = PROMISE_STATUS_REJECTED;
					this.reason = reason;
					this.onrejected.forEach(fn => {
						fn(reason);
					})
				})
			}
		}
		executor(resolve, rejected)
	}
	// 接下来为了Promise能够多次调用 进行优化
	then(onfufilled, onrejected) {
		if (this.status === PROMISE_STATUS_FULFILLED) {
			onfufilled(this.value);
		}
		if (this.status === PROMISE_STATUS_REJECTED) {
			onrejected(this.value);
		}
		if (this.status === PROMISE_STATUS_PENDING) {
			this.onfufilled.push(onfufilled);
			this.onrejected.push(onrejected);
		}

	}
}
  • 因为改进之后,需要存储resolverejectedvaluereason值,所以我们定义了这两个值
  • 为了满足多次调用,我们需要将promise中的onfulfilledonrejected改为数组存储以用来满足我们的多次调用
  • 定时器的问题我这边说下,因为setTimeout属于宏任务,在同步代码执行完毕之后,会接着执行微任务,所以宏任务是最后来执行的,所以也就造成了promise中的代码执行完了,但是包裹在定时器中的then方法没有获取到结果
  • 所以呢,在这里我决定让处于定时器中的代码直接执行而不压入数组中去,因为定时器之前的代码已经执行完毕了,promise的状态也已经发生了改变,所以我就在then方法中判断promise的状态,如果是fulfilledrejected状态的话,传过来的函数就直接执行

04-then方法的链式调用

要想实现链式调用,那么then方法肯定是将Promise对象又给返回出来了,说到这了大家有没有思路呢?

const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";

class ZXPromise {
	constructor(executor) {
		this.status = PROMISE_STATUS_PENDING;
		this.value = undefined;
		this.reason = undefined;
		this.onfufilled = [];
		this.onrejected = [];
		const resolve = (value) => {
			if (this.status === PROMISE_STATUS_PENDING) {
				queueMicrotask(() => {
					if (this.status !== PROMISE_STATUS_PENDING) return
					this.status = PROMISE_STATUS_FULFILLED;
					this.value = value;
					this.onfufilled.forEach(fn => {
						fn(value);
					});
				})
			}
		}
		const rejected = (reason) => {
			if (this.status === PROMISE_STATUS_PENDING) {
				queueMicrotask(() => {
					if (this.status !== PROMISE_STATUS_PENDING) return
					this.status = PROMISE_STATUS_REJECTED;
					this.reason = reason;
					this.onrejected.forEach(fn => {
						fn(reason);
					})
				})
			}
		}
		try{
			executor(resolve, rejected)
		}catch(err){
			console.log(err);
		}
		
	}
	
	then(onfufilled, onrejected) {
		return new ZXPromise((resolve, rejected) => {
			if (this.status === PROMISE_STATUS_FULFILLED) {
				try {
					//如果then中有返回值,就会作为下一个then所接收的值
					const value = onfufilled(this.value);
					resolve(value);
				} catch (err) {
					rejected(err);
				}
				
			}
			if (this.status === PROMISE_STATUS_REJECTED) {
				try {
					const value = onrejected(this.value);
					resolve(value);
				} catch (err) {
					rejected(err);
				}
			}
			if (this.status === PROMISE_STATUS_PENDING) {
				try {
					this.onfufilled.push(() => {
						const value = onfufilled(this.value);
						resolve(value);
					});
				} catch (err) {
					rejected(err);
				}
				try {
					this.onrejected.push(() => {
						const value = onrejected(this.value);
						resolve(value);
					});
				} catch (err) {
					rejected(err);
				}
			}
		})
	}
}


const promise = new ZXPromise((resolve, rejected) => {
	resolve("123");
	rejected("wushichu");
})
promise.then((res) => {
	console.log("res1:", res);
	return "abc";
}, (err) => {
	console.log("err1", err);
}).then((res) => {
	console.log("res2", res);
}, (err) => {
	console.log("err2", err);
})
  • 变化最大的就是then方法了,大家可以看到我又把ZXPromise返回出去了,代码中我写的很清楚了

05-catch方法实现

catch方法实际上是then第二个参数的语法糖,说到这里大家有没有明白什么呢?

const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";

const execFnWithCatchError = (execFn, value, resolve, reject) => {
    try {
        const result = execFn(value);
        resolve(result);
    } catch (err) {
        reject(err);
    }
}

class ZXPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onfufilled = [];
        this.onrejected = [];
        const resolve = (value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_FULFILLED;
                    this.value = value;
                    this.onfufilled.forEach(fn => {
                        fn(value);
                    });
                })
            }
        }
        const rejected = (reason) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_REJECTED;
                    this.reason = reason;
                    this.onrejected.forEach(fn => {
                        fn(reason);
                    })
                    return this.reason;
                })
            }
        }
        executor(resolve, rejected)
    }

    then(onfufilled, onrejected) {
        //这一段是为了将错误代码传递下去的
        const defaultOnRejected = err => { throw err }
        onrejected = onrejected || defaultOnRejected
        return new ZXPromise((resolve, rejected) => {
            if (this.status === PROMISE_STATUS_FULFILLED && onfufilled) {
                execFnWithCatchError(onfufilled, this.value, resolve, rejected);
            }
            if (this.status === PROMISE_STATUS_REJECTED && onrejected) {
                execFnWithCatchError(onrejected, this.reason, resolve, rejected);
            }
            if (this.status === PROMISE_STATUS_PENDING) {
                if (onfufilled)
                    this.onfufilled.push(() => {
                        execFnWithCatchError(onfufilled, this.value, resolve, rejected);
                    });
                if (onrejected) {
                    this.onrejected.push(() => {
                        execFnWithCatchError(onrejected, this.reason, resolve, rejected);
                    });
                }
            }
        })
    }
    catch(onrejected) {
        return this.then(undefined, onrejected);
    }
}
  • 大家可以看到catch代码实际上就只有一行,就是将then方法进行了调用,是不是相当简单呢
  • 然后我觉得那个try catch代码重复性比较高,所以我将它提取了出来复用
  • 然后大家看下那个then里面的开头,onrejected函数被给予了一个默认值,如果then没有传递第二个参数,那么会被赋予一个错误处理函数的默认值,抛出错误后,会自动被try catch捕获进行reject,这样子错误会被层层传递,一直到最后被catch函数所执行.

06-finally的实现

finally就是要在最后执行的函数,无论什么情况,实现起来也是非常简单

    finally(fn) {
        return this.then(() => { fn() }, () => { fn() });
    }
  • 在类中加上这一段代码就好了,因为finally是无法接收任何resolve和rejected的值的,所以我们在传递的函数中执行fn,就是避免resolve的值和rejected的值被传递到finally上去

07-完整代码总览

const PROMISE_STATUS_PENDING = "PROMISE_STATUS_PENDING";
const PROMISE_STATUS_FULFILLED = "PROMISE_STATUS_FULFILLED";
const PROMISE_STATUS_REJECTED = "PROMISE_STATUS_REJECTED";

const execFnWithCatchError = (execFn, value, resolve, reject) => {
    try {
        const result = execFn(value);
        resolve(result);
    } catch (err) {
        reject(err);
    }
}

class ZXPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onfufilled = [];
        this.onrejected = [];
        const resolve = (value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_FULFILLED;
                    this.value = value;
                    this.onfufilled.forEach(fn => {
                        fn(value);
                    });
                })
            }
        }
        const rejected = (reason) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_REJECTED;
                    this.reason = reason;
                    this.onrejected.forEach(fn => {
                        fn(reason);
                    })
                    return this.reason;
                })
            }
        }
        executor(resolve, rejected)
    }

    then(onfufilled, onrejected) {
        //这一段是为了将错误代码传递下去的
        const defaultOnRejected = err => { throw err }
        onrejected = onrejected || defaultOnRejected
        return new ZXPromise((resolve, rejected) => {
            if (this.status === PROMISE_STATUS_FULFILLED && onfufilled) {
                execFnWithCatchError(onfufilled, this.value, resolve, rejected);
            }
            if (this.status === PROMISE_STATUS_REJECTED && onrejected) {
                execFnWithCatchError(onrejected, this.reason, resolve, rejected);
            }
            if (this.status === PROMISE_STATUS_PENDING) {
                if (onfufilled)
                    this.onfufilled.push(() => {
                        execFnWithCatchError(onfufilled, this.value, resolve, rejected);
                    });
                if (onrejected) {
                    this.onrejected.push(() => {
                        execFnWithCatchError(onrejected, this.reason, resolve, rejected);
                    });
                }
            }
        })
    }
    catch(onrejected) {
        return this.then(undefined, onrejected);
    }

    finally(fn) {
        return this.then(() => { fn() }, () => { fn() });
    }
}
  • 大家可以自行进行测试

分享名称:[手写系列] 带你实现一个简单的Promise
文章出自:http://ybzwz.com/article/dsojhic.html