前端异步手写Promise,深入理解Promise

2 阅读6分钟

 一、 Promise详解

a. Promise核心特性(最关键)

1.状态不可逆(三种状态,一旦定型无法改变) Promise 存在且仅存在三种状态,状态的转换只有两种单向路径,不可逆、不可重复。

  • 初始状态:PromiseState=pending(进行中)
  • 执行resolve(),成功状态:PromiseState=fulfilled(已完成)
  • 执行reject(),失败状态:PromiseState=rejected(已拒绝)
  • 转换路径 1:pendingfulfilled(调用 resolve() 触发)
  • 转换路径 2:pendingrejected(调用 reject() 或执行器内部抛出错误触发)
  • 示例:一旦状态变为 fulfilled,后续再调用 reject() 也无法改变状态,结果值也会保持不变。
  • Promise中有throw的话,就相当于执行了reject

2.结果值唯一(状态定型后,携带唯一的结果 / 原因)

  • then接收两个回调,一个成功回调,一个失败回调
  • 状态变为 fulfilled 时,会携带一个唯一的「成功结果值」(value),存储在 Promise 内部,可通过 then 成功回调获取;
  • 状态变为 rejected 时,会携带一个唯一的「失败原因」(reason),存储在 Promise 内部,可通过 then 失败回调或 catch 获取;
  • 这个值一旦确定,就不会被修改,后续访问都会得到相同的结果。

3.异步执行特性(回调函数异步触发,符合微任务规范) Promise 的回调(then/catch/finally永远不会同步执行

  • 原生 Promise 回调属于微任务,会在当前同步代码执行完毕后、下一轮宏任务执行前触发(优先于 setTimeout 等宏任务);
  • 这一特性避免了回调嵌套导致的「执行顺序混乱」,也符合 JavaScript 事件循环机制。

4.链式调用(支持无限链式 then ,实现异步流程串行化)

  • then() 方法必然会返回一个新的 Promise 实例(不是原来的 Promise 实例),这是链式调用的基础;

  • 新 Promise 的状态和结果,由上一个 then 回调的返回值决定:

  1. 若回调返回非 Promise 值(基本类型、普通对象等),新 Promise 状态为 fulfilled,结果为该返回值;

  2. 若回调抛出错误,新 Promise 状态为 rejected,结果为该错误信息;

  3. 若回调返回一个 Promise 实例,新 Promise 会沿用该实例的状态和结果;

  • 链式调用解决了传统回调函数的「回调地狱」问题,让异步流程更清晰。

b. Promise 关键行为

1.回调队列管理(支持多个 then 绑定,依次触发) 一个 Promise 实例可以多次调用 then() 方法,绑定多个成功 / 失败回调。

  • 若 Promise 状态未定型(pending),回调会被存入对应的「回调队列」(成功回调队列 / 失败回调队列);
  • 若 Promise 状态已定型,回调会被异步推入微任务队列,等待执行;
  • 状态定型后,后续绑定的回调也能正常触发,获取到已确定的结果值。

2.错误冒泡(链式调用中,错误会向下传递,直到被捕获)

  • 链式调用中,任何一个环节抛出错误(或 Promise 变为 rejected),如果当前没有对应的 onRejected 回调,错误会向下冒泡,被后续的 catchthen 失败回调捕获;
  • 这一特性让错误处理更统一,无需在每个 then 中都绑定失败回调。

3.值穿透( then 传入非函数参数时,会自动透传结果)then() 方法传入的不是函数(如 nullundefined 等),会被自动忽略,等价于传递了一个「透传函数」,结果会直接传递给下一个 then

  • 示例:promise.then(null).then(res => console.log(res)) 等价于 promise.then(res => res).then(res => console.log(res))
  • 这一特性保证了链式调用的连贯性,即使中间某个 then 未传入有效回调,也不会中断流程。

c. Promise 使用优势

  1. 解决「回调地狱」(Callback Hell):将嵌套的异步回调转为扁平的链式调用,代码可读性和可维护性大幅提升;
  2. 统一异步错误处理:通过 catch 实现全局错误捕获,避免传统回调中「错误处理分散」的问题;
  3. 支持多种异步流程控制:通过 Promise.all()Promise.race()Promise.allSettled() 等静态方法,轻松实现「并行执行」「竞速执行」等复杂异步流程;
  4. async/await 的基础:async/await 是 Promise 的语法糖,只有理解 Promise,才能熟练掌握 async/await 的使用。

d. 总结

Promise 的核心可以概括为 3 个关键词:

  1. 状态不可逆:三种状态,单向转换,结果唯一;
  2. 异步执行:回调属于微任务,避免同步阻塞和顺序混乱;
  3. 链式调用:返回新 Promise,解决回调地狱,支持复杂异步流程。

这些特性共同让 Promise 成为 JavaScript 异步编程的核心方案,也是后续学习 async/await、前端工程化异步流程的基础。

二、手写Promise

class MyPromise{
	static PENDING = 'pending';
	static FULFILLED = 'fulfilled';
	static REJECTED = 'rejected';
	static FUNCTION = 'function';
	constructor(executor){
		this.initValue();
		this.initBind();
		try{
			executor(this.resolve,this.reject);
		}catch(err){
			this.reject(err)
		}
	}
	initValue(){
		this.PromiseState = MyPromise.PENDING;
		this.PromiseResult = null;
		this.onFulfilledCallbacks = [];
		this.onRejectedCallbacks = [];
	}

	initBind(){
		//绑定this的原因
		//resolve 和 reject 是 MyPromise 实例的方法(属于 this),
        //但当我们把它们作为参数传给 executor 函数
        //(也就是 new MyPromise((resolve, reject) => { ... }) 里的回调)时,
        //函数的 this 指向会丢失。
		
        //不绑定,执行时 this 是 undefined(严格模式下)
		//this.resolve 是一个函数引用,当把它单独传给 executor 时,
        //它就成了 “裸函数”,执行时失去了原本的 this 上下文;
		//在严格模式下(ES6 类默认开启严格模式),裸函数执行的 this 是 undefined,
        //而非 MyPromise 实例;
		//所以 resolve 里的 this.PromiseState 会因为 this 是 undefined 而报错。

		this.resolve = this.resolve.bind(this);
		this.reject = this.reject.bind(this);
	}
	resolve(value){
		if(this.PromiseState !== MyPromise.PENDING) return//状态不可逆
		this.PromiseState = MyPromise.FULFILLED;//成功
		this.PromiseResult = value;
		while(this.onFulfilledCallbacks.length){
			this.onFulfilledCallbacks.shift()(this.PromiseResult);
		}
	}
	reject(reason){
		if(this.PromiseState !== MyPromise.PENDING) return//状态不可逆
		this.PromiseState = MyPromise.REJECTED;//失败
		this.PromiseResult = reason;
		while(this.onRejectedCallbacks.length){
			this.onRejectedCallbacks.shift()(this.PromiseResult);
		}
	}

	then(onFulfilled,onRejected){

		//保证链式调用不中断
		onFulfilled = typeof onFulfilled === MyPromise.FUNCTION ? onFulfilled :val => val;
		//为什么要判断onFulfilled是否是函数
		//链式调用中断:如果用户没传 onFulfilled(比如 promise.then().then(...)),
        //onFulfilled 是 undefined,执行 undefined(this.PromiseResult) 会直接报错,
        //链式调用直接崩掉;
		
        //值无法透传:Promise 设计的核心是 “值的穿透”
        //如果上一个 then 没有处理成功值,下一个 then 应该能拿到这个值。
		//比如 promise.then().then(res => console.log(res)),
        //第二个 then 必须能拿到第一个 Promise 的结果,
        //而这个 “透传” 就是靠 val => val 这个默认回调实现的。
		onRejected = typeof onRejected === MyPromise.FUNCTION ?onRejected :reason => {throw reason}

		var thenPromise = new MyPromise((resolve,reject)=>{
			const resovePromise = cb=>{
				// 异步执行:符合 Promise 规范
				queueMicrotask(()=>{
					try{
						const x = cb(this.PromiseResult);//成功onFulfilled(this.PromiseResult)
						if(x === thenPromise){throw new TypeError('not self')}
							//异步操作,如果上一个 then 返回的是一个新 Promise,
                            //下一个 then 必须等待这个新 Promise 完成,才能拿到它的结果。
						if(x instanceof MyPromise){
							//x 是一个 “未完成 / 已完成” 的 Promise,
                            //它的结果需要通过 then 才能获取
							x.then(resolve,reject)
						}else{
							//x 是普通值(数字、字符串、对象、null 等),
                            //它的结果是 “立即确定” 的,不需要等待
							resolve(x)
						}
					}catch(e){
						this.reject(e)
					}
				})
				
			}
		
			if(this.PromiseState === MyPromise.FULFILLED){
				resovePromise(onFulfilled);
			}else if(this.PromiseState === MyPromise.REJECTED){
				resovePromise(onRejected);
			}else if(this.PromiseState === MyPromise.PENDING){
				this.onFulfilledCallbacks.push(resovePromise.bind(this,onFulfilled));
				this.onRejectedCallbacks.push(resovePromise.bind(this,onRejected));
			}
		})
		return thenPromise
	}
}

最后

这是《JavaScript系列》第9篇,将持续更新。

小伙伴如果喜欢我的分享,可以动动您发财的手关注下我,我会持续更新的!!!
您对我的关注、点赞和收藏,是对我最大的支持!欢迎关注、评论、讨论和指正!