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

  • A+
所属分类:Web前端
摘要

学习之前 需要先对Promise有个基本了解哦,这里都默认大家都是比较熟悉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() });     } } 
  • 大家可以自行进行测试