异步执行的诀窍
为了简化模型,我们在例子中只讨论 resolve ,reject 方法的实现思路与 resolve 类似。
简化版的异步执行由两个部分组成:resolve 与 then 。我们先来看一个标准的Promise:
const p = new Promise((resolve) => {
setTimeout(() => {
resolve(1)
}, 2000)
})
p.then(res => console.log(res))
因为 resolve 在函数 setTimeout 中,因此它会延迟执行,而 then 方法是一个普通函数,它会同步执行,要想在 then 方法中获取 resolve 延迟执行的结果,有一个技巧是将 then 方法中的函数存储起来而不是立刻执行,等计时器到时,再用 resolve 执行这个函数。
根据这个思路,我们定义一个类 MyPromise ,将 then 方法存储到 this.callback 变量中。
class MyPromise {
constructor(fn) {
this.callback = null
fn(this._resolve)
}
_resolve =(val) => {
this.callback(val)
}
then = (onfulfilled) => {
this.callback = onfulfilled
}
}
const p = new MyPromise((resolve) => {
setTimeout(() => {
resolve(1)
}, 2000)
})
p.then(res => console.log(res)) // 1
我们会同步执行 MyPromise 中的函数,因此我们会在 class 的初始化阶段 constructor 执行它。执行时我们会为它提供内部定义的 _resolve 函数,它的责任是接收用户提供的参数,取出存储的 this.callback 函数并用这个参数去执行存储中的方法函数。
可以这样写链式调用吗?
关于链式调用,最直接的想法是当我们调用一个类方法时,返回一个 class 自身,比如:
class Adder {
value = 0;
add = (val) => {
this.value += val
return this
}
getValue = () => {
return this.value
}
}
const adder = new Adder()
const value = adder
.add(1)
.add(2)
.add(3)
.getValue()
console.log(value); // 6
如果根据这个思路,我们可以在调用 .then 方法时返回 class 自身,从而调用下一个 then 方法,我们可以将这些方法全部存储到一个数组 this.callbacks 中。
在执行 resolve 时将 this.callbacks 数组中每个函数的执行结果传递给下一个函数。于是我们的代码看起来会像这样:
class MyPromise {
constructor(fn) {
this.callbacks = []
this.value = null
fn(this._resolve)
}
_resolve = (val , key = 0) =>{
if(key === this.callbacks.length - 1){
return this.callbacks[key](val)
}else{
const res = this.callbacks[key](val)
return this._resolve(res , key + 1)
}
}
then = (onfulfilled) => {
this.callbacks.push(onfulfilled)
return this
}
}
const p = new MyPromise((resolve) => {
setTimeout(() => {
resolve(1)
}, 2000)
})
p
.then(res => res + 1)
.then(res => res + 1)
.then(res => console.log(res)) // 3
可以这样写吗?当然不行!我们使用 Promise 是执行异步方法的,就像这样:
//使用Promise
function getUserId(url) {
return new Promise(function (resolve) {
//异步请求
http.get(url, function (id) {
resolve(id)
})
})
}
getUserId('some_url').then(function (id) {
//do something
return getNameById(id); // 新的Promise
}).then(function (name) {
//do something
return getCourseByName(name); // 新的Promise
}).then(function (course) {
//do something
})
我们上面的写法只能支持同步函数的链式调用,如果遇到一个新的Promise ,它对这样的东西完全没有处理能力:
p
.then(res => new MyPromise((resolve) => {
setTimeout(() => {
resolve(res + 1)
}, 2000)
}))
.then(res => res + 1)
.then(res => console.log(res))
// [object Object]1
// c:\Users\lemon\Downloads\Telegram Desktop\b.js:13
// const res = this.callbacks[key](val)
// ^
// TypeError: this.callbacks[key] is not a function
// at _resolve (c:\Users\lemon\Downloads\Telegram Desktop\b.js:13:44)
// at Timeout._onTimeout (c:\Users\lemon\Downloads\Telegram Desktop\b.js:34:13)
// at listOnTimeout (node:internal/timers:559:17)
// at processTimers (node:internal/timers:502:7)
我们会先尝试去修改这种写法,但很快会发现无从下手,我们只能推翻重来,首先我们需要让then方法返回一个新的Promise对象。
让then方法返回一个新的Promise对象
让then方法返回一个新的Promise对象,新的Promise对象上拥有自身的 then 方法,因此通过这种方法可以不断进行链式调用,这是我们改写后的 then 方法:
then = (onfulfilled) => {
return new MyPromise((resolve) => {
this.callback = { onfulfilled, resolve }
})
}
这串代码比较让人疑惑的是我们除了要异步执行的 onfulfilled 函数外,同时将新 Promise 的 resolve 方法也加入了存储行列。
resolve 方法有两个功能:
- 接收上一个Promise
resolve出的结果 - 用该结果执行当前 Promise 的
callback函数
也就是说 resolve 是链接两个 Promise 的关键,我们获取新 Promise 的 resolve 函数是用其接收 onfulfilled 的执行结果,将该结果传递给下一个 Promise . 因此引出了 resolve 函数的定义:
_resolve = (value) => {
const { onfulfilled, resolve } = this.callback
const res = onfulfilled(value)
resolve(res)
}
因为最后一个 then 方法创建的新 Promise 没有相应的 then 方法,因此没有定义自身的 callback 方法,所以在执行最后一个 resolve 时需要对空的 callback 做一下处理:
_resolve = (value) => {
if (!this.callback) return
const { onfulfilled, resolve } = this.callback
const res = onfulfilled(value)
resolve(res)
}
现在我们来试一下同步的 then 调用,它应该是可以正常工作的:
class MyPromise {
constructor(fn) {
this.callback = null
fn(this._resolve)
}
_resolve = (value) => {
if (!this.callback) return
const { onfulfilled, resolve } = this.callback
const res = onfulfilled(value)
resolve(res)
}
then = (onfulfilled) => {
return new MyPromise((resolve) => {
this.callback = { onfulfilled, resolve }
})
}
}
const p = new MyPromise((resolve) => {
setTimeout(() => {
resolve(1)
}, 2000)
})
p
.then(res => res + 1)
.then(res => res + 1)
.then(res => console.log(res)) // 3
但这不是我们的最终目的,我们还需要兼容异步的 then 方法调用,比如如下的形式:
p
.then(res => new MyPromise((resolve) => {
setTimeout(() => {
resolve(res + 1)
}, 2000)
}))
.then(res => console.log(res))
和刚才的区别是,当异步方法执行完成后, onfulfilled 方法会返回一个 Promise 对象而非一个普通数据类型。根据我们刚才讨论的,这个 Promise 对象会被新Promise的 resolve 所接收。因此我们在 resolve 方法中对这种情况进行单独处理:
_resolve = (value) => {
if (!this.callback) return
if (value instanceof MyPromise) {
...
}
const { onfulfilled, resolve } = this.callback
const res = onfulfilled(value)
resolve(res)
}
我们要做的,仅仅只是通过 then 方法取出这个返回的 Promise 对象中真正 resolve 出的值,然后用它走和之前一样的逻辑:
_resolve = (value) => {
if (value instanceof MyPromise) {
const p = value;
p.then((val) => {
this.exec(val)
})
return
}
if (!this.callback) return
this.exec(value)
}
exec = (value) => {
const { onfulfilled, resolve } = this.callback
const res = onfulfilled(value)
resolve(res)
}
完整代码:
class MyPromise {
constructor(fn) {
this.callback = null
fn(this._resolve)
}
_resolve = (value) => {
if (value instanceof MyPromise) {
const p = value;
p.then((val) => {
this.exec(val)
})
return
}
if (!this.callback) return
this.exec(value)
}
exec = (value) => {
const { onfulfilled, resolve } = this.callback
const res = onfulfilled(value)
resolve(res)
}
then = (onfulfilled) => {
return new MyPromise((resolve) => {
this.callback = { onfulfilled, resolve }
})
}
}
const p = new MyPromise((resolve) => {
setTimeout(() => {
resolve(1)
}, 2000)
})
p
.then(res => new MyPromise((resolve) => {
setTimeout(() => {
resolve(res + 1)
}, 2000)
}))
.then(res => console.log(res)) // 2
上面代码执行的流程图: