Js手写简单的Promise、Promise.all、Promise.race
最近准备面试,看八股文手撕Promise,想着也没没事,就看看怎么实现,不得不说现在前端真的卷~~ 全部基本都是转载一位大佬的,这里只简单的整理、实现了Promise、Promise.all、Promise.race,复杂的请移步大佬文章:原文链接,现在找个实习工作都这么难找吗,难道真的是人们说的前端已死???
Promise
- 首先我们在调用 Promise 时,会返回一个 Promise 对象。
- 构建 Promise 对象时,需要传入一个 executor 函数,Promise 的主要业务流程都在 executor 函数中执行。
- 如果运行在 excutor 函数中的业务执行成功了,会调用 resolve 函数;如果执行失败了,则调用 reject 函数。
- Promise 的状态不可逆,同时调用 resolve 函数和 reject 函数,默认会采取第一次调用的结果。
以上简单介绍了 Promise 的一些主要的使用方法,结合 Promise/A+ 规范,我们可以分析出 Promise 的基本特征:
- promise 有三个状态:
pending
,fulfilled
,orrejected
;「规范 Promise/A+ 2.1」 new promise
时, 需要传递一个executor()
执行器,执行器立即执行;executor
接受两个参数,分别是resolve
和reject
;- promise 的默认状态是
pending
; - promise 有一个
value
保存成功状态的值,可以是undefined/thenable/promise
;「规范 Promise/A+ 1.3」 - promise 有一个
reason
保存失败状态的值;「规范 Promise/A+ 1.5」 - promise 只能从
pending
到rejected
, 或者从pending
到fulfilled
,状态一旦确认,就不会再改变; - promise 必须有一个
then
方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, 和 promise 失败的回调 onRejected;「规范 Promise/A+ 2.2」 - 如果调用 then 时,promise 已经成功,则执行
onFulfilled
,参数是promise
的value
; - 如果调用 then 时,promise 已经失败,那么执行
onRejected
, 参数是promise
的reason
; - 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调
onRejected
;
按照上面的特征,我们试着勾勒下 Promise 的形状:
class Promise {
constructor (excutor) {
// 规定状态
this.state = 'peding'
// 保存 `resolve(res)` 的res值
this.value = undefined
// 保存 `reject(err)` 的err值
this.reason = undefined
// 调用此方法就是成功
let resolve = (value) => {
// 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
if (this.state === 'peding') {
this.state = 'fulfilled'
this.value = value
}
}
// 调用此方法就是失败
let reject = (reason) => {
// 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
if (this.state === 'peding') {
this.state = 'rejected'
this.reason = reason
}
}
try {
// 立即执行,将 resolve 和 reject 函数传给使用者
excutor(resolve, reject)
} catch (error) {
// 若出错,直接调用reject
reject(error)
}
}
// 包含一个 then 方法,并接收两个参数 onFulfilled、onRejected
then (onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value)
}
if (this.state === 'rejected') {
onRejected(this.reason)
}
}
}
写完代码我们可以测试一下:
let p1 = new Promise((resolve, reject) => {
resolve('ok1');
}).then(res => {
console.log(res);
})
控制台输出:
ok1
现在我们已经实现了一个基础版的 Promise,但是还不要高兴的太早噢,这里我们只处理了同步操作的 promise。如果在 executor()
中传入一个异步操作的话呢,我们试一下:
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok2')
}, 1000);
}).then(res => {
console.log(res);
})
执行测试脚本后发现,promise 没有任何返回。
因为 promise 调用 then 方法时,当前的 promise 并没有成功,一直处于 pending 状态。所以如果当调用 then 方法时,当前状态是 pending,我们需要先将成功和失败的回调分别存放起来,在executor()
的异步任务被执行时,触发 resolve 或 reject,依次调用成功或失败的回调。
结合这个思路,我们优化一下代码:
class Promise {
constructor (excutor) {
// 规定状态
this.state = 'peding'
// 保存 `resolve(res)` 的res值
this.value = undefined
// 保存 `reject(err)` 的err值
this.reason = undefined
// 存放成功的回调
this.successCB = []
// 依次将对应的函数执行
this.faildCB = []
// 调用此方法就是成功
let resolve = (value) => {
// 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
if (this.state === 'peding') {
this.state = 'fulfilled'
this.value = value
this.successCB.forEach(f => f())
}
}
// 调用此方法就是失败
let reject = (reason) => {
// 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
if (this.state === 'peding') {
this.state = 'rejected'
this.reason = reason
this.faildCB.forEach(f => f())
}
}
try {
// 立即执行,将 resolve 和 reject 函数传给使用者
excutor(resolve, reject)
} catch (error) {
// 若出错,直接调用reject
reject(error)
}
}
// 包含一个 then 方法,并接收两个参数 onFulfilled、onRejected
then (onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value)
}
if (this.state === 'rejected') {
onRejected(this.reason)
}
if (this.state === 'peding') {
this.successCB.push(() => { onFulfilled(this.value) })
this.faildCB.push(() => { onRejected(this.reason) })
}
}
}
测试一下:
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok2')
}, 1000);
}).then(res => {
console.log(res);
})
控制台等待 1s
后输出:
ok2
ok!大功告成,异步问题已经解决了!
熟悉设计模式的同学,应该意识到了这其实是一个发布订阅模式,这种收集依赖 -> 触发通知 -> 取出依赖执行
的方式,被广泛运用于发布订阅模式的实现。
Promise.all
promise.all 是解决并发问题的,多个异步并发获取最终的结果(如果有一个失败则失败)。
Promise.all = function (promises) {
let list = []
let count = 0
function handle(i, data, resolve) {
list[i] = data
count++
if (count == promises.length) {
resolve(list)
}
}
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(res => {
handle(i, res, resolve)
}, err => reject(err))
}
})
}
测试一下:
let p1 = new Promise((resolve, reject) => {
resolve('ok1');
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok2');
}, 1000);
})
let p3 = new Promise((resolve, reject) => {
resolve('ok3')
})
Promise.all([p1,p2,p3]).then(data => {
console.log(data);
}, err => {
console.log('reject', err);
})
控制台等待 1s
后输出:
['ok1', 'ok2', 'ok3']
测试一下返回失败:
let p1 = new Promise((resolve, reject) => {
resolve('ok1');
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok2');
}, 1000);
})
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('err');
}, 1000);
})
Promise.all([p1,p2,p3]).then(data => {
console.log(data);
}, err => {
console.log('reject:', err);
})
控制台等待 1s
后输出:
reject: err
Promise.race
Promise.race 用来处理多个请求,采用最快的(谁先完成用谁的)。
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(res => {
resolve(res)
}, err => {
reject(err)
})
}
})
}
测试一下:
let p1 = new Promise((resolve, reject) => {
resolve('ok1');
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok2');
}, 1000);
})
let p3 = new Promise((resolve, reject) => {
resolve('ok3');
})
Promise.race([p1,p2,p3]).then(data => {
console.log(data);
}, err => {
console.log('reject', err);
})
控制台输出:
ok1
此文只实现简单的Promise,完整的请移步前面发的链接去查看,最后祝大家生活愉快,谢谢!!
@zwx