从零开始 - 40行代码实现一个简单Promise函数
一个遵循PromiseA+规范的函数,个人认为解决了callback异步回调地狱的问题,注意是callback方式的回调地狱,promise本身也可以存在回调地狱,需配合ES7特性async、await才能做到完全解决回调地狱。
Promise主要特点
- Promise 会有三种状态,「进⾏中」「已完成」和「已拒绝」,进⾏中状态可以更改为已完成或已拒绝,已经更改过状态后⽆法继续更改(例如从已完成改为已拒绝)。
- Promise 构造之后需要传⼊⼀个函数,它接受两个参数,执⾏第⼀个参数之后就会改变当前 promise 为「已完成」状态,执⾏ 第⼆个参数之后就会变为「已拒绝」状态。
- 通过
.then
⽅法,即可在上⼀个 promise 达到已完成 时继续执⾏下⼀个函数或 promise。同时通过 resolve 或 reject 时传⼊参数,即可给下⼀个函数或 promise 传⼊初始值。 - 已拒绝的 promise,后续可以通过 .catch ⽅法或是 .then ⽅法的第⼆个参数或是 try catch 进⾏捕获。
根据特性梳理结构
class simplePromise {
constructor(handleFn) { // 构造时传入一个函数
this.status = 'pending' // 有状态控制
handleFn(resolve, reject) // 该函数接收两个参数
}
then() { // 它有一个then方法
return new simplePromise(() => { // 返回的是promise实例then才得以串联起来
if (this.status === 'pending') { // 状态一旦改变就不可修改
// TODO
}
})
}
// 一些常用的静态方法
catch() {}
resolve() {}
reject() {}
}
实现resolve
这是Promise的第一个关键点,主要在于逻辑的判断,如何将then串联起来,以及最后收集依赖
class SimplePromise {
constructor(handleFn) {
this.status = 'pending' // 标记状态
this.fulfilledList = [] // 任务队列
handleFn(params => { })
}
then(onFulfilled, onRejected) { // then()接收两个函数
// 返回一个Promise
return new SimplePromise((onNextFulfilled, onNextRejected) => {
function finalFn(params) {
// 如果不是函数,跳过执行下一步
if (typeof onFulfilled !== 'function') {
onNextFulfilled(params)
} else {
// 如果是函数,先接收其返回结果
const res = onFulfilled(params)
// 判断是否为Promise,也可以判断有没有then方法typeof res.then === 'function'
if (res && res instanceof SimplePromise) {
res.then(onNextFulfilled) // 继续执行Promise
} else {
onNextFulfilled(res) // 不是Promise继续执行下一步
}
}
}
if (this.status === 'pending') {
this.fulfilledList.push(finalFn) // 收集依赖
}
})
}
}
第二个关键点在于,用户传入的函数,在构造时会立即执行,但收集依赖是在then方法中,所以需要将传入的函数放到异步队列中去执行
class SimplePromise {
constructor(handleFn) {
this.status = 'pending' // 标记状态
this.fulfilledList = [] // 任务队列
handleFn((val) => {
setTimeout(() => { // 抛进异步队列,保证不会立即执行
if (this.status !== 'pending') { return } // 状态一旦确定再无法变更
this.status = 'fulfilled' // 执行就改变状态
this.fulfilledList.forEach(fn => fn(val))// 开始执行函数
this.fulfilledList = [] // 释放
}, 0)
})
}
then(onFulfilled, onRejected) { ..... }
catch() { }
resolve() { }
reject() { }
}
- 接下来将reject也实现进去,handleFn为用户传入的函数,其接收两个参数resolve和reject,我们分别执行了两个队列里收集的任务。
- 而then中则创建了一个工厂函数,用于生成两种收集依赖的方法。
完整代码
class SimplePromise {
constructor(handleFn) {
this.status = 'pending' // 标记状态
this.fulfilledList = [] // 任务队列
this.rejectedList = []
handleFn(this.trigger.bind(this, 'fulfilled'), this.trigger.bind(this, 'rejected'))
}
trigger(status, val) {
setTimeout(() => { // 抛进异步队列,保证不会立即执行
if (this.status !== 'pending') return; // 状态一旦确定再无法变更
this.status = status // 状态变更
this[`${status}List`].forEach(fn => fn(val)) // 开始执行函数
}, 0)
}
then(onFulfilled, onRejected) {
return new SimplePromise((onNextFulfilled, onNextRejected) => {
function createFinalFn(prev, next) {
return function (params) {
if (typeof prev !== 'function') {
next(params)
} else {
const res = prev(params)
res && res instanceof SimplePromise ? res.then(next) : next(res)
}
}
}
if (this.status === 'pending') {
this.fulfilledList.push(createFinalFn(onFulfilled, onNextFulfilled))
this.rejectedList.push(createFinalFn(onRejected, onNextRejected))
}
})
}
catch(onRejected) { // 返回reject
return this.then(null, onRejected)
}
static resolve(val) { // 直接成功执行的结果
return new SimplePromise(resolve => resolve(val))
}
static reject(val) { // 暴露出失败结果,或许可以用来做Promise的中断
return new SimplePromise((resolve, reject) => reject(val))
}
}
至此大概40行的代码实现了一个简单的Promise,写个例子测试下
const p = new SimplePromise((resolve, reject) => {
resolve('success');
}).then(res => {
console.log('ok', res)
}, err => {
console.log('no', err)
})
// ok success
注:本例中实现的简单Promise无法跑通全部官方测试用例,只是对实现规范中的核心部分进行一次编写练习。
主要理解以上两个关键点其实就理解了Promise的核心部分,面试中可应对大部分Promise原理考察,如果面试官要你用手写完整的Promise实现,那你应该反问他贵公司是不是从来不使用电脑办公。
最后用上我们手写的SimplePromise来运行一道综合题加深对Promise的认识
var promise = new SimplePromise(function(resolve, reject){
setTimeout(function() {
resolve(1);
}, 1000)
})
promise.then(() => {
return SimplePromise.resolve(2);
}).then((n) => {
console.log('我是第一个:结果是',n)
});
promise.then(() => {
return 2
}).then((n) => {
console.log('我是第二个:结果是',n)
});
promise.then(2).then((n) => {
console.log('我是第三个:结果是',n)
});
输出结果:
我是第二个: 结果是2
我是第三个: 结果是1
我是第一个: 结果是2
以上就是文章的全部内容了,感谢看到这里!如果觉得写得还不错,对你有所帮助或启发,别忘了点赞收藏关注“一键三连”哦~ 我是茶无味的一天(m.palxp.cn)(公众号: 品味前端),一名平凡的前端 Developer,希望与你共同成长~