Promise一些思考
我们之前一直都是在强调如何手写promise
,但是和工作上的使用场景没有联系上,这篇文章主要是从工作角度和实用性上探讨promise
。
一、接口请求
我们在工作中,经常会碰到这种情况。一个文件导出请求的api封装,然后另外一个文件在某个时机(初始化)的时候执行函数,发起请求,然后再then
函数中进行一些处理。
let getData = function () {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: 'hcc'
})
}, 1000)
})
}
getData().then(res => {
console.log(res)
})
思考: 为什么可以这样呢?Promise
是基于什么逻辑可以做到这样呢?这里有三个点需要思考
new Promise()
返回了什么resolve(xxx)
是什么和做了什么then()
做了什么
二、 new Promise做了什么
new Promise(fn)
接受一个函数参数,在没有resolve
或者reject
的时候,都是pending
状态,返回了本质上是一个对象,有一些方法(then
, catch
, finally
)。
class Promise {
constructor(fn) {
this.state = PENDING
this.result = null
this.callbacks = []
}
then(onFulfilled, onRejected) {}
****
}
三、resolve()是什么和做了什么
我们知道Promise
的有一个函数参数,接受2个参数,那这2个函数到底是什么呢?
class Promise {
constructor(fn) {
let done = false
const onFulfilled = (value) => transition(this, FULFILLED, value)
const resolve = (value) => {
if (done) return
done = true
resolvePromise(this, value, onFulfilled, onRejected)
}
fn(resolve, reject)
}
}
const resolvePromise = (promise, x, resolve, reject) => {
resolve(x)
}
const transition = (promise, state, result) => {
if (promise.state !== PENDING) return
promise.state = state
promise.result = result
// 这里处理then注册的callback,因此需要通过其他platform Api实现异步调用
runAsync(() => {
while (promise.callbacks.length) {
handleCallback(promise.callbacks.shift(), state, result) // 处理callback
}
})
}
从上面的简单代码中,我们可以看出我们在调用resolve(data)
的时候,本质上只做了2件事情
- 将我们的promise实例的state改成
FULFILLED
和result改成传递进来的参数data
- 在下一个宏任务中执行promise实例的
callbacks
,
问题: 那么问题来了,我们什么时候给实例的callbacks添加回到函数呢?
四、 then() 做了什么
通过上面的调用resolve()我们明白,在调用resolve()后,promise实例的状态已经从pending
变成fulfilled
,并将传递的值赋值到result
主要的作用有以下几点
- 每次
then()
函数都会返回一个新的promise实例 - 将then函数接受的2个函数参数进行处理,如果是
pending
状态就放入**callbacks
**中,如果不是的话,就下一个任务执行
then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
const callback = { resolve, reject, onFulfilled, onRejected }
if (this.state === PENDING) {
this.callbacks.push(callback)
} else {
runAsync(() => handleCallback(callback, this.state, this.result))
}
})
}
五、正常场景流程分析
5.1 我们直接执行promise和单个then
let getData = function () {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: 'hcc'
})
}, 1000)
})
}
getData().then(res => {
console.log(res)
})
-
当执行getData()的时候,首先会执行
Promise
的构造函数constructor
, 触发setTimeout(发起请求),将函数放入下一个宏任务中等待执行,此时promise实例还是pending
状态 -
返回Promise的实例,执行then方法,传入
onFulfilled
的回调函数,注意此时还没有执行resolve(xxx)
,通过上面then
函数做了什么,我们可以知道,此时then()
会将onFulfilled
的处理放入实例的callbacks中
return new Promise((resolve, reject) => { const callback = { resolve, reject, onFulfilled, onRejected } if (this.state === PENDING) { this.callbacks.push(callback) } }
-
then()
执行完成后,开始执行下一个宏任务,会调用resolve({name: 'hcc' })
// 1. 实际resolve执行的是这个函数 const onFulfilled = (value) => transition(this, FULFILLED, value) const resolve = (value) => { if (done) return done = true resolvePromise(this, value, onFulfilled, onRejected) } // 2. 接下来会执行resolvePromise,进行一些对value值的判断,这里我们传递的是要给对象没有额外的处理,省略掉 const resolvePromise = (promise, x, resolve, reject) => { resolve(x) } // 3. 实际就是执行了(value) => transition(this, FULFILLED, value) const transition = (promise, state, result) => { if (promise.state !== PENDING) return promise.state = state promise.result = result // 这里处理then注册的callback,因此需要通过其他platform Api实现异步调用 runAsync(() => { while (promise.callbacks.length) { handleCallback(promise.callbacks.shift(), state, result) } }) }
transition()
会将当前promise实例从pending
转换成fulfilled
,此时promise.callbacks
中已经有我们上一个then()
传入的callbacks
值,所以会执行到handleCallback
-
handleCallback()
的执行, 其中callback对象的四个参数是如下的来源const handleCallback = (callback, state, result) => { const { resolve, reject, onFulfilled, onRejected } = callback try { if (state === FULFILLED) { isFunction(onFulfilled) ? resolve(onFulfilled(result)) : resolve(result) } } catch (error) { reject(error) } }
-
resolve/reject
: 第二步中then()
新返回的promise的resolve/reject -
onFulfilled/onRejected
: 是第二步中then()
的接受的2个入参, -
由于我们传递的是一个函数
onFulfilled
, 所以会执行onFulfilled
并传入实例result
的值,并将返回值传递给then()
返回新的promise的resolve中// then接受的第一个参数 -> onFulfilled res => { console.log(res) }
-
这里一个整个流程就完成了。这里做一个总结:
5.2 直接执行promise并链式调用then
let getData = function () {
return new Promise((resolve) => {
setTimeout(() => {
resolve(1)
}, 1000)
})
}
getData().then(res => {
console.log(res)
return res + 1
}).then(res => {
console.log('res -- 2', res)
})
我们看上面的代码,链式调用then()
方法,那这个流程又是怎么样的呢?
从5.1的第四点中,我们知道了then()
会返回一个新的promise实例
, 那么链式调用,就会调用上一个then()
方法返回的实例,并传入一个回调函数(onFulfilled
)。
此时全部都是同步执行,所有的Promise都处于pending状态,所以回调任务都被放入没有给promsie的callbacks
中。等待同步任务执行完成,主线程空闲,去执行第一个resolve(1)
, 从5.1的第四点中,我们知道了resolve(1)
,本质上会去执行transition
, 改变每一个promise
实例的状态和值, 异步调用callbacks
。
整体流程总结:
- 执行
getData()
, 执行构造函数,执行传入的参数Fn, 并执行setTimeout
, 放入宏任务队列,等待执行 - 返回一个Promise实例(A),执行A的
then()
方法,传入一个函数作为参数(onFulfilled
) then()
函数中,再次执行新的new Promise()
, 构造函数里面进行封装callback
参数(包含4个参数), 但是由于此时A还处于pending
状态, 所以会将封装的callback
放入实例A的callbacks中,返回新的promise实例B- 执行实例B的then方法,由于实例B也处于
pending
状态,所以此时B实例的then()
方法执行的逻辑也是将参数onFulfilled
放入实例B自身的callbacks
属性中,再次返回一个新的promise实例C, 结束主线程执行。 - 然后接下来开始下一个宏任务,执行实例A的
resolve()
。- 将实例A的状态从
pending
改成fulfilled
- 改变实例A的
result
值(为resolve
的入参) - 新增异步队列,处理实例A中的callbacks值,结束resolve的调用,主线程结束
- 下一个队列开始执行(刚刚新增的处理A的callbacks值的函数),执行
handleCallback
, resolve执行完毕
const { resolve, reject, onFulfilled, onRejected } = callback if (state === FULFILLED) { isFunction(onFulfilled) ? resolve(onFulfilled(result)) : resolve(result) }
- 将实例A的状态从
- 此时实例A的resolve执行完毕,由于onFulfilled是一个函数(之前A实例调用的then方法传入的第一个函数),会执行callback中的
resolve
(执行then方法的时候,新增的实例B的resolve), 将函数的值传递给实例B的resolve
, 进行下一轮的调用。 - 实例B的
resolve
开始调用, 进行复制和新的状态改变,然后异步执行实例B的callbacks,至此,流程结束。
5.3 promise值传递
最近我在研究小程序的预请求的时候,有一个场景是,将一个promise用一个对象进行存放,当需要的时候进行获取,然后获取接口的返回值。例如这样
// 文件A
let getData = function () {
return new Promise((resolve) => {
setTimeout(() => {
resolve(1)
}, 1000)
})
}
// 文件B 在之后的某一个时间需要去处理
let a = getData()
setTimeout(() => {
a.then(res => {
console.log(res)
})
}, 3000)
这也是符合promsie
承诺的意思,我调用后,在之后的某一个时间需要,然后取值的时候可以取到。那这个过程的内部其实是什么样的呢?
整体流程:
- 执行
getData()
, 然后执行内部的new Promise(Fn)
。 - 执行
Fn()
, 调用setTimeout,将resolve放入宏任务队列中等待下一次执行,返回promise的实例,赋值给a, 主流程执行完毕。 - 遇到下一个
setTimeout()
, 将回调函数放入宏队列,等待执行,主线程结束。 - 主线程执行完毕,开始检查宏任务队列,1s后,执行
resolve(1)
, 将a的状态改成fulfilled
并进行赋值操作,由于此时a中的callbacks的值为空,所以不会有执行什么操作,主线程结束。 - 主线程执行完毕,开始检查宏任务队列,3s后,执行a的
then
函数, 传入onFulfilled
,返回一个新的promise, 然后promise的内部执行传入的函数fn, 由于此时a.then()
中的this
(a)的state的状态已经不再是pending
了, 所以会将() => handleCallback(callback, this.state, this.result)
放入宏任务队中,等待执行,主线程结束。then(onFulfilled, onRejected) { return new Promise((resolve, reject) => { const callback = { resolve, reject, onFulfilled, onRejected } if (this.state === PENDING) { this.callbacks.push(callback) } else { runAsync(() => handleCallback(callback, this.state, this.result)) } }) }
- 主线程执行完毕,开始检查宏任务队列,执行
handleCallback
, 进行值的处理,将result
的值放入到onFulfilled
执行,这样我们就可以拿到之前的resolve
的值了。isFunction(onFulfilled) ? resolve(onFulfilled(result)) : resolve(result)
5.4 传入的值为Promise的情况
我们在工作中也会遇到下面这种场景,resolve()
的值接收到了一个promise
, 这种情况下,我们应该怎么理解我们的代码的执行呢?
let getData = function () {
return new Promise((resolve) => {
setTimeout(() => {
resolve(Promise.resolve(1))
}, 1000)
})
}
let a = getData()
a.then(res => {
console.log(res)
})
主要思路解析:
- 这里前期还是和上面的一样,执行
getData
- 然后执行
Promise
的构造函数,执行then
方法将onFulfilled
放入实例a的callbacks
中 - 下一个宏任务执行到a实例的
resolve()
事件,本质上会执行resolvePromise
发现是一个promise
类型const resolvePromise = (promise, x, resolve, reject) => { if (isPromise(x)) { return x.then(resolve, reject) } resolve(x) }
- 这里的x就是我们传递进来的
Promise.resolve(1)
的一个promise对象(b
),这里会调用b
的then()
方法,而resolve传递的是实例a的 - 执行实例b的
then
方法, 由于实例b已经是fulfilled
状态,所以执行它的then()
方法会直接将处理callback
的函数放入宏队列中。此时的callback中的4个参数分别代表如下:- resolve/reject: 代表then函数新建的promsie实例
- onFulfilled/onRejected: 代表a实例的promise构造函数中封装的
onFulfilled/onRejected
then(onFulfilled, onRejected) { return new Promise((resolve, reject) => { const callback = { resolve, reject, onFulfilled, onRejected } runAsync(() => handleCallback(callback, this.state, this.result)) }) }
- 接下来执行b实例的
handleCallback
,由于b实例的状态已经是fulfilled
, 所以会执行这一行代码const handleCallback = (callback, state, result) => { const { resolve, reject, onFulfilled, onRejected } = callback isFunction(onFulfilled) ? resolve(onFulfilled(result)) : resolve(result) }
- 将b实例的result(
1
)传递给onFulfilled
,而onFulfilled
函数正好是实例a的resolve的封装,这样就链接上了,然后正常走实例a的resolve流程,修改状态和值,执行callbacks
六,总结
上面讲解了promise的内部实现,本质上resolve()
就是promise包装的一个函数,用于改变promsie实例的值,then()
会根据promsie的状态,对回调函数进行处理。
最后附上完整代码,用于和上面的知识点进行对比~
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
const isObject = (obj) => obj !== null && typeof obj === 'object'
const isFunction = (fn) => typeof fn === 'function'
const isPromise = (p) => p instanceof Promise
const isThenable = (obj) => (isObject(obj) || isFunction(obj)) && 'then' in obj && isFunction(obj.then)
let i = 1
class Promise {
constructor(fn) {
this.state = PENDING
this.result = null
this.callbacks = []
this.name = i++
let done = false
const onFulfilled = (value) => transition(this, FULFILLED, value)
const onRejected = (reason) => transition(this, REJECTED, reason)
const resolve = (value) => {
console.log('constructor-resolve-------' , this.name)
if (done) return
done = true
resolvePromise(this, value, onFulfilled, onRejected)
}
const reject = (reason) => {
if (done) return
done = true
onRejected(reason)
}
try {
fn(resolve, reject)
} catch (error) {
reject(error)
}
}
// 2.2.7 then方法必须返回一个Promise
then(onFulfilled, onRejected) {
console.log('---constructor--then--------', this.name)
return new Promise((resolve, reject) => {
const callback = { resolve, reject, onFulfilled, onRejected }
if (this.state === PENDING) {
this.callbacks.push(callback)
} else {
runAsync(() => handleCallback(callback, this.state, this.result, this.name))
}
})
}
catch(onRejected) {
return this.then(null, onRejected)
}
finally(onFinally) {
return this.then(
(res) => Promise.resolve(onFinally()).then(() => res),
(err) =>
Promise.resolve(onFinally()).then(() => {
throw err
})
)
}
// implement es6
static resolve(value) {
if (isPromise(value)) return value
if (isThenable(value)) return new Promise((resolve, reject) => value.then(resolve, reject))
return new Promise((resolve) => resolve(value))
}
static reject(reason) {
return new Promise((_, reject) => reject(reason))
}
}
// promise 状态迁移的公共函数
const transition = (promise, state, result) => {
console.log('-transition', promise.name)
if (promise.state !== PENDING) return
promise.state = state
promise.result = result
// 这里处理then注册的callback,因此需要通过其他platform Api实现异步调用
runAsync(() => {
console.log('-runAsync-transition', state, promise.name)
while (promise.callbacks.length) {
handleCallback(promise.callbacks.shift(), state, result, promise.name)
}
})
}
const handleCallback = (callback, state, result, name) => {
const { resolve, reject, onFulfilled, onRejected } = callback
// 2.2
try {
if (state === FULFILLED) {
console.log('-handleCallback', state, name)
isFunction(onFulfilled) ? resolve(onFulfilled(result)) : resolve(result)
} else if (state === REJECTED) {
isFunction(onRejected) ? resolve(onRejected(result)) : reject(result)
// 如果有注册catch或者onRejected
}
} catch (error) {
reject(error)
}
}
// 2.3 Promise Resolution Procedure
const resolvePromise = (promise, x, resolve, reject) => {
console.log('resolve-------' , promise.name)
// 2.3.1
if (x === promise) {
return reject(new TypeError(''))
}
// 2.3.2
if (isPromise(x)) {
console.log('resolve-------' , promise.name)
return x.then(resolve, reject)
}
// 2.3.3 if x is an object or function
// Note that the specification does not require x to be thenable here.
if (isFunction(x) || isObject(x)) {
// 2.3.3.2 retrieving the x.then
try {
const then = x.then
// 3.5
if (isFunction(then)) {
return new Promise(then.bind(x)).then(resolve, reject)
}
} catch (error) {
return reject(error)
}
}
resolve(x)
}
let getData = function () {
return new Promise((resolve) => {
setTimeout(() => {
resolve(Promise.resolve(1))
}, 1000)
})
}
let a = getData()
a.then(res => {
console.log(res)
})
/*setTimeout(() => {
a.then(res => {
console.log(res)
})
}, 3000)*/
const runAsync = (cb) => {
setTimeout(() => {
cb()
}, 0)
}
/*
const runAsyncWithMutationObserver = (cb) => {
const observer = new MutationObserver(cb)
const textNode = document.createTextNode('1')
observer.observe(textNode, { characterData: true })
textNode.data = '2'
}
const runAsyncWithMessageChannel = (cb) => {
const channel = new MessageChannel()
channel.port1.onmessage = cb
channel.port2.postMessage(1)
}
*/