- Published on
理解并手写一个Promise
- Authors
- Name
- Joy Peng
Promise是什么?
想象一下,你是一位顶尖歌手,粉丝没日没夜地询问你下首歌什么时候发。
为了从中解放,你承诺(promise)会在单曲发布的第一时间发给他们。你给了粉丝们一个列表。他们可以在上面填写他们的电子邮件地址,以便当歌曲发布后,让所有订阅了的人能够立即收到。即便遇到不测,例如录音室发生了火灾,以致你无法发布新歌,他们也能及时收到相关通知。
每个人都很开心:你不会被任何人催促,粉丝们也不用担心错过歌曲发行。
这是我们在编程中经常遇到的事儿与真实生活的类比:
- “生产者代码(producing code)”会做一些事儿,并且会需要一些时间。例如,通过网络加载数据的代码。它就像一位“歌手”。
- “消费者代码(consuming code)”想要在“生产者代码”完成工作的第一时间就能获得其工作成果。许多函数可能都需要这个结果。这些就是“粉丝”。
- Promise 是将“生产者代码”和“消费者代码”连接在一起的一个特殊的 JavaScript 对象。用我们的类比来说:这就是就像是“订阅列表”。“生产者代码”花费它所需的任意长度时间来产出所承诺的结果,而 “promise” 将在它(译注:指的是“生产者代码”,也就是下文所说的 executor)准备好时,将结果向所有订阅了的代码开放。
这段话很形象地解释了Promise
的概念,它能够很好地支持异步编程,避免了回调地狱的问题。
接着来看下面这样一个例子:
const fetchValue = fetch('/api/get-data')
console.log(fetchValue)
当我们调用fetch
时,它会启动网络请求,这是一个异步操作,JavaScript线程不会停止并等待,代码继续运行。
但是fetch()
函数实际返回了什么?不可能是请求的结果,因为请求还没完成。实际上它返回一个Promise
对象,你可以把它理解为一张借条,上面写着“我承诺会在未来的某个时间点给你一个结果”。
Promise的基本特点
executor
Promise
是一个构造函数,传递给new Promise
的函数被称为executor
,当Promise
对象被创建时,executor
会立即执行。
当executor
获得了结果,就会调用以下回调之一:- resolve(value)
:如果操作成功完成,带有结果value
。- reject(error)
:如果出现错误,带有错误error
。
Promise
对象的三种状态
Promise
始终处于以下三种状态之一:
pending
:初始状态,既不是成功,也不是失败状态。fulfilled
:意味着操作成功完成。rejected
:意味着操作失败。
状态改变只能是pending
->fulfilled
或者pending
->rejected
,状态一旦改变就不能再变。
简单总结一下,由new Promise
构造器返回的promise
对象具有以下内部属性:
- state: 最开始是
pending
,然后变为fulfilled
或rejected
。 - result: 最开始是
undefined
,然后在resolve(value)
被调用时变为value
,在reject(error)
被调用时变为error
。
then()和catch()方法
我们希望在Promise
对象状态改变时执行一些操作,这时我们可以使用then()
方法,它接收两个参数:onFulfilled
和onRejected
,分别对应Promise
对象状态变为fulfilled
和rejected
时的回调函数。 我们也可以只传入一个参数,这个参数既是onFulfilled
也是onRejected
。
如果Promise
对象状态变为rejected
,我们也可以使用catch()
方法来处理错误。
fetch('/api/get-data')
.then((response) => {
console.log(response)
// Response { type: 'basic', status: 200, ...}
})
.catch((error) => {
console.log(error)
})
[!Tip] 如果您以前使用过 Fetch API,您可能已经注意到需要第二步,才能以 JSON 格式实际获取我们需要的数据:
fetch('/api/get-data') .then((response) => { return response.json() }) .then((data) => { console.log(data) })
这是因为
response.json()
也返回一个Promise
对象,所以我们需要再次使用then()
方法。
fetch()
产生的promise
在浏览器从服务器接收到第一个字节时就会resolve
,然后返回response.json
的promise
,这个promise
在浏览器接收到整个响应并解析完JSON时才会resolve
。
手写简易的Promise
根据目前的理解,我们可以尝试手写一个简易的Promise
。
// 定义三种状态
const state = {
PENDING: 'pending',
FULFILLED: 'fulfilled',
REJECTED: 'rejected',
}
class MyPromise {
// 构造函数,在new Promise的时候立刻执行,通常需要花费一些时间
constructor(executor) {
this.state = state.PENDING // 初始化状态为pending
this.value = undefined // 初始化value为undefined
this.onFulfilledCbs = [] // 存放onFulfilled回调函数,因为可能有多个then
this.onRejectedCbs = [] // 存放onRejected回调函数
try {
executor(this.resolve, this.reject) //立刻执行executor
} catch (error) {
this.reject(error) // 如果执行executor时出错,直接reject
}
}
runAllCallbacks = () => {
// 如果状态是fulfilled,依次执行存入的onFulfilled函数们
if (this.state === state.FULFILLED) {
this.onFulfilledCbs.forEach((cb) => cb(this.value))
this.onFulfilledCbs = [] // 执行完后清空
}
// 如果状态是rejected,依次执行存入的onRejected函数们
if (this.state === state.REJECTED) {
this.onRejectedCbs.forEach((cb) => cb(this.value))
this.onRejectedCbs = []
}
}
// resolve函数,因为使用箭头函数,所以这里的this指向MyPromise实例;如果使用普通函数,需要在new Promise时绑定this
resolve = (value) => {
if (this.state !== state.PENDING) return
if (value instanceof MyPromise) {
value.then(this.resolve, this.reject)
return
}
this.state = state.FULFILLED
this.value = value
this.runAllCallbacks()
}
// reject函数
reject = (error) => {
if (this.state !== state.PENDING) return
if (error instanceof MyPromise) {
error.then(this.resolve, this.reject)
return
}
this.state = state.REJECTED
this.value = error
this.runAllCallbacks()
}
// then方法,接受两个参数,onFulfilled和onRejected
then(onFulfilled, onRejected) {
if (onFulfilled == null && onRejected == null) return
if (this.state == state.FULFILLED) {
onFulfilled(this.value)
}
if (this.state == state.REJECTED) {
onRejected(this.value)
}
if (this.state == state.PENDING) {
this.onFulfilledCbs.push(onFulfilled)
this.onRejectedCbs.push(onRejected)
}
this.runAllCallbacks()
}
catch(onRejected) {
this.then(null, onRejected)
}
}
// 测试
const myPromise = new MyPromise((resolve, reject) => {
console.log(this, 'this')
setTimeout(() => {
resolve('Hello, Promise!') // 1秒后resolve
}, 1000)
})
// 多个then,每个then都接收了一个回调函数,这些回调函数会被存入对应的数组,等待resolve或reject后依次执行
myPromise.then((value) => {
console.log('First then:', value) // 1秒后输出Hello, Promise!
})
myPromise.then((value) => {
console.log('Second then:', value) // 1秒后输出Hello, Promise!
})
myPromise.then((value) => {
console.log('Third then:', value) // 1秒后输出Hello, Promise!
})
链式调用
promise还有一个重要的特性,就是可以链式调用。这是因为在官方的Promise
实现中,每次调用then
方法都会返回一个新的Promise
对象。 正如我们前面看过的fetch
的例子,response.json()
也返回一个Promise
对象,所以我们可以继续使用then
方法。
fetch('/api/get-data')
.then((response) => {
return response.json()
})
.then((data) => {
console.log(data)
})
那么我们怎么修改上面自己实现的简易Promise
,使其支持链式调用呢?
首先在then
方法这个地方要做个改装,让它总是返回一个promise
// then方法,接受两个参数,onFulfilled和onRejected,告诉Promise状态变为fulfilled或rejected时该如何处理
then(onFulfilled, onRejected) {
// 无论如何,then方法都会返回一个新的Promise
return new MyPromise((resolve, reject) => {
const fulfilledCb = (value) => {
try {
const result = onFulfilled ? onFulfilled(value) : value // 如果onFulfilled存在,执行onFulfilled,否则直接返回value
if (result instanceof MyPromise) {
// 如果onFulfilled返回的是Promise实例,继续then,等待resolve或reject
result.then(resolve, reject)
} else {
// 如果不是Promise实例,直接resolve
resolve(result)
}
} catch (error) {
reject(error)
}
}
const rejectedCb = (error) => {
try {
const result = onRejected ? onRejected(error) : error // 如果没有传入onRejected,直接返回error
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
resolve(result)
}
} catch (error) {
reject(error)
}
}
if(this.status == STATE.PENDING) {
this.onFulfilledCbs.push(fulfilledCb)
this.onRejectedCbs.push(rejectedCb)
}
if(this.status == STATE.FULFILLED) {
queueMicrotask(()=> fulfilledCb(this.value))
}
if (this.status == STATE.REJECTED) {
queueMicrotask(()=> rejectedCb(this.value))
}
})
}
测试:
const myPromise = new MyPromise((resolve, reject) => {
console.log(this, 'this')
setTimeout(() => {
resolve('Hello, Promise!') // 1秒后resolve
}, 1000)
})
myPromise
.then((value) => {
console.log('First then:', value) // 1秒后输出Hello, Promise!
return 'First then'
})
.then((value) => {
console.log('Second then:', value) // 1秒后输出First then
return 'Second then'
})
.then((value) => {
console.log('Third then:', value) // 1秒后输出Second then
return 'Third then'
})
还有一个可以改善的地方,就是给resolve
和reject
加上setTimeout
或queueMicrotask
,这样可以保证then
方法总是在resolve
或reject
之后执行。
setTimeout
用的是宏任务队列,queueMicrotask
用的是微任务队列,微任务队列的优先级高于宏任务队列,所以queueMicrotask
会更好点。
finally
finally的作用是在上一个Promise结束后,无论结果是fulfilled还是rejected,都会执行一段代码。它接收一个回调函数,不接收参数,也不关心Promise的状态。
// 例子:
const myPromise = new MyPromise((resolve, reject) => {
console.log(this, 'this')
setTimeout(() => {
resolve('Hello, Promise!') // 1秒后resolve
}, 1000)
})
myPromise
.then((value) => {
console.log('First then:', value) // 1秒后输出Hello, Promise!
return 'First then'
})
.finally(() => {
console.log('Finally!') // 1秒后输出Finally!
})
.then((value) => {
console.log('Second then:', value) // 1秒后输出First then
return 'Second then'
})
在我们的MyPromise
添加finally
方法:
finally(onFinally) {
return this.then(
(value) => {
onFinally()
return value
},
(error) => {
onFinally()
throw error
}
)
}
resolve and reject
Promise
里还有一个静态方法Promise.resolve
和Promise.reject
,它们分别返回一个Promise
对象,Promise.resolve
返回一个fulfilled
状态的Promise
对象,Promise.reject
返回一个rejected
状态的Promise
对象。
// 例子:
const resolvedPromise = MyPromise.resolve('Hello, Promise!')
resolvedPromise.then((value) => {
console.log(value) // Hello, Promise!
})
const rejectedPromise = MyPromise.reject('Error!')
rejectedPromise.catch((error) => {
console.log(error) // Error!
})
在MyPromise
中添加resolve
和reject
方法:
static resolve(value) {
return new MyPromise((resolve) => {
resolve(value)
})
}
static reject(error) {
return new MyPromise((_, reject) => {
reject(error)
})
}
all
Promise.all
接收一个Promise
对象数组,当所有Promise
对象都变为fulfilled
状态时,返回一个fulfilled
状态的Promise
对象,结果是一个数组,数组的顺序和传入的Promise
对象数组的顺序一致。
// 例子:
const promise1 = new MyPromise((resolve) => {
setTimeout(() => {
resolve('Promise 1')
}, 1000)
})
const promise2 = new MyPromise((resolve) => {
setTimeout(() => {
resolve('Promise 2')
}, 2000)
})
MyPromise.all([promise1, promise2]).then((values) => {
console.log(values) // ['Promise 1', 'Promise 2']
})
在MyPromise
中添加all
方法:
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = []
let count = 0
promises.forEach((promise, index) => {
promise.then((value) => {
results[index] = value
count++
if (count === promises.length) {
resolve(results)
}
}).catch(reject)
})
})
}
allSettled
Promise.allSettled
接收一个Promise
对象数组,当所有Promise
对象都变为fulfilled
或rejected
状态时,返回一个fulfilled
状态的Promise
对象,结果是一个数组,数组的顺序和传入的Promise
对象数组的顺序一致。
// 例子:
const promise1 = new MyPromise((resolve) => {
setTimeout(() => {
resolve('Promise 1')
}, 1000)
})
const promise2 = new MyPromise((_, reject) => {
setTimeout(() => {
reject('Promise 2')
}, 2000)
})
MyPromise.allSettled([promise1, promise2]).then((values) => {
console.log(values) // [{ status: 'fulfilled', value: 'Promise 1' }, { status: 'rejected', reason: 'Promise 2' }]
})
在MyPromise
中添加allSettled
方法:
static allSettled(promises) {
return new MyPromise((resolve) => {
const results = []
let count = 0
promises.forEach((promise, index) => {
promise.then(
(value) => {
results[index] = { status: STATE.FULFILLED, value }
},
(reason) => {
results[index] = { status: STATE.REJECTED, reason }
}
).finally(() => {
count++
if (count === promises.length) {
resolve(results)
}
})
})
})
}
race
Promise.race
接收一个Promise
对象数组,当其中任意一个Promise
对象变为**fulfilled
或rejected
**状态时,返回一个fulfilled
状态的Promise
对象,结果是第一个变为fulfilled
或rejected
状态的Promise
对象的结果。
// 例子:
const promise1 = new MyPromise((resolve) => {
setTimeout(() => {
resolve('Promise 1')
}, 1000)
})
const promise2 = new MyPromise((_, reject) => {
setTimeout(() => {
reject('Promise 2')
}, 2000)
})
MyPromise.race([promise1, promise2]).then((value) => {
console.log(value) // Promise 1
})
在MyPromise
中添加race
方法:
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach((promise) => {
promise.then(resolve).catch(reject)
})
})
}
any
Promise.any
接收一个Promise
对象数组,当其中任意一个Promise
对象变为fulfilled
状态时,返回一个fulfilled
状态的Promise
对象,结果是第一个变为fulfilled
状态的Promise
对象的结果。如果所有Promise
对象都变为rejected
状态,返回一个rejected
状态的Promise
对象,结果是所有Promise
对象的错误。
// 例子:
const promise1 = new MyPromise((_, reject) => {
setTimeout(() => {
reject('Promise 1')
}, 1000)
})
const promise2 = new MyPromise((resolve) => {
setTimeout(() => {
resolve('Promise 2')
}, 2000)
})
MyPromise.any([promise1, promise2]).then((value) => {
console.log(value) // Promise 2
})
在MyPromise
中添加any
方法:
static any(promises) {
return new MyPromise((resolve, reject) => {
let count = 0
promises.forEach((promise) => {
promise.then(resolve).catch((error) => {
count++
if (count === promises.length) {
reject(error)
}
})
})
})
}
使用promise如何避免回调地狱?
Promise如何避免回调地狱?
什么是回调地狱?回调地狱是指多个回调函数嵌套调用,导致代码难以阅读和维护。例如:
console.log(3)
setTimeout(() => {
console.log(2)
setTimeout(() => {
console.log(1)
setTimeout(() => {
console.log('Happy New Year!')
}, 1000)
}, 1000)
}, 1000)
上述代码实现了一个新年倒计时,这是一个典型的回调地狱。使用Promise
可以很好地解决这个问题,代码变得更加清晰易读:
const countdown = (time, message) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(message)
resolve()
}, time)
})
}
countdown(1000, 3)
.then(() => countdown(1000, 2))
.then(() => countdown(1000, 1))
.then(() => console.log('Happy New Year!'))
如果再使用async
和await
,代码会更加简洁:
const countdown = (time, message) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(message)
resolve()
}, time)
})
}
const newYear = async () => {
await countdown(1000, 3)
await countdown(1000, 2)
await countdown(1000, 1)
console.log('Happy New Year!')
}
参考资料
[1] https://zh.javascript.info/promise-basics [2] https://www.joshwcomeau.com/javascript/promises/ [3] https://www.youtube.com/watch?v=1l4wHWQCCIc&t=448s