1.Promise的执行原理为什么可以解决回调地狱以及怎么解决的
在学习异步编程时,我终于搞懂了 JavaScript 的执行机制。JS 引擎是一行一行同步执行代码的,如果某段代码执行很慢,就会阻塞后面的代码。为了解决这个问题,JS 引入了异步机制,比如用 setTimeout 包裹耗时函数,让它延后执行,不影响主线程继续运行。
但这也带来新问题:异步的返回值无法立即获取,调用函数时只能拿到 undefined。于是我们传入回调函数来“等待结果”,在异步任务完成后再手动调用回调,把结果传出来。然而一旦对返回值要进行多步操作,就会嵌套多个回调,形成“回调地狱”。
为了解决这个问题,Promise 诞生了。它可以存储异步返回值,并通过 .then() 获取结果,还支持链式调用,让异步流程变得清晰优雅。关键在于,.then() 返回的也是一个新的 Promise 实例,我们可以继续 .then(),实现连续处理。
那为什么 Promise 就能“存值再取值”?因为 JS 引擎的执行机制分为调用栈 、微任务队列和宏任务队列。同步代码直接进入调用栈执行,异步任务(如定时器)进宏队列,而 Promise.then() 属于微队列,在当前同步代码执行完后优先执行。这样一来,resolve 存值时在调用栈中完成,等 .then() 所在的微任务开始执行时,值已经准备好,自然就能拿到了。
总结一下,异步的本质是“排队”,而 Promise 则是用微任务的优先机制,把“取值”安排在“存值”之后,完美避开了时间错位的问题。理解这一点后,手写一个 Promise 实现也就顺理成章了。
resolve 是在什么时候被调用的?被包裹在异步操作中(如 setTimeout)时,是在对应宏任务执行的时候调用。
resolve 属于宏任务还是微任务?不是任务队列本身的任务,它是宏任务中执行的一段同步代码。
****resolve 是怎么“存”值的?改变内部状态为 fulfilled,并将值保存起来,然后将 .then() 回调推入微任务队列。
****为什么 .then() 能保证在 resolve 之后?因为 .then() 的回调是放入微任务队列,只有 resolve() 被执行,状态变了,才会触发微任务执行。
2.手写Promise
在开始写代码之前,我先来口述一下自己理解的手写 Promise 的思路。
首先我们知道,Promise 本质上是一个类。平时我们这样使用它:
const p = new Promise((resolve, reject) => {resolve('hah') })
p.then(r => console.log(r))
所以我们第一步是创建一个类:
class MyPromise {}
这个类的构造函数接受一个函数类型的参数,并且会立即执行这个函数。我们通过参数形式把 resolve 和 reject 这两个方法传给用户,用户调用这两个函数就能存储数据。
既然要存值,我们就需要在实例上定义一个属性,比如 this.PromiseResult,用来存储用户传进来的数据。
接下来是 .then() 的实现。我们希望 then 方法能在用户调用 resolve 存值之后,把这个值“拿出来”。那就需要我们在 .then() 里注册一个回调函数,等数据准备好了再执行。
到这儿,我们就完成了最简单的 Promise 架构。
但还不够。
我们发现:resolve 应该只能调用一次。所以要添加一个 status 状态值,比如初始是 'pending',只有在状态是 'pending' 时才允许存储数据。状态一旦变成 'fulfilled' 或 'rejected',就不能再修改。
然后我们进一步发现:如果 resolve 是异步的,比如在 setTimeout 里调用的,那 .then() 方法执行时,数据还没准备好,这时候就拿不到值。
怎么办?
我们就把 .then() 注册时传进来的回调函数,暂时缓存起来,等 resolve 执行的时候再调用它。最简单的做法是设置一个属性,比如 this.callback = fn,在 resolve 时调用 this.callback(this.PromiseResult)。
后来我们又发现,这样只能支持单次调用 .then() 。我们希望支持链式 .then(),那就需要把回调函数缓存成一个数组,每次调用 .then() 就 push 进去,等 resolve 的时候统一 forEach 执行。
至此,我们实现了“多个 then 调用”的功能。
但问题还没完,我们还希望支持 链式调用,也就是说每次 .then() 都返回一个新的 Promise 实例,允许我们继续调用 .then():
那该怎么做呢?
我们只需要让 .then() 方法的返回值是 new MyPromise(...),并在它的回调函数内部调用 resolve(...),把前一个 .then() 处理完的值传进去。就这样,通过包裹和传递,我们就实现了链式的值传递和异步流程控制。
这就是我自己推导出的 Promise 实现思路。下面,我们就用代码一步一步实现它。
const PROMISE_STATE ={
PENDING:0,
FULFILLED:1,
REJECTED:2
}
class MyPromise{
#PromiseResult
#PromiseState =0
#cbs = []
constructor(cb){
cb(this.#resolve.bind(this),this.#reject)
}
#resolve(value){
if(this.#PromiseState !== PROMISE_STATE.PENDING) return
this.#PromiseResult = value
this.#PromiseState=1
queueMicrotask(()=>{
this.#cbs.forEach(cb=>cb())
})
}
#reject(){}
then(cb1,cb2){
return new MyPromise((resolve,reject)=>{
if(this.#PromiseState===PROMISE_STATE.PENDING){
this.#cbs.push(()=>{
resolve( cb1(this.#PromiseResult))
})
}else if(this.#PromiseState === PROMISE_STATE.PENDING){
queueMicrotask(()=>{
resolve(cb1(this.#PromiseResult))
})
}
})
}
}
const p = new MyPromise((resolve,reject)=>{
setTimeout(()=>{
resolve('数据')
})
//resolve('数据')
})
p.then(r=>
{
console.log('数据1',r)
return '猪八戒'
}).then(r=>console.log(r))
p.then(r=>console.log('数据2',r))
编辑
手写 Promise 的本质,其实就是解决“回调地狱”问题,或者说:希望异步代码也能像同步一样,把值存下来,供后续逻辑自由处理。
实现思路并不复杂——一个类、几个属性、一些回调函数——关键在于抽丝剥茧、不断思考、发现问题、解决问题。这不仅锻炼了代码能力,也打磨了我们对 JS 异步机制的理解。