手写一个面试中经常被问到的promise,一看就会(简单版)
小二,上酒!
天涯的渡口, 一人陪马儿清瘦, 多么富有, 天下了心中。
介绍
promise相信大家在开发中都使用过,面试中如果提到ES6必问,初衷是用来解决回调地狱的,在开发中更常用async和await,他们也可以相结合使用,promise有一个很美的中文解释叫《期约》。
基本用法
new Promise()
Promise 构造方法接收一个回调方法,会在内部调用,然后回传回两个回调方法 promise 有三个状态 pending(初始状态) fulfilled(成功) rejected(失败)
const p = new Promise((resolve,reject) => {
//这里写一些业务逻辑
//成功的回调 修改当前实例状态为fulfilled
resolve(1);
//失败的回调 修改当前实例状态为rejected
reject(1)
})
then()
then方法接收2个回调函数,第一个是成功的回调,第二个是失败的回调。 如果promise的状态为fulfilled则第一个回调被执行,否则第二个被执行。
p.then(res => {
console.log(res);
},reason => {
console.log(reason)
})
开搞
我们就来实现基本的promise、then方法和all方法(在实现之前会介绍)
MyPromise类
//先定义三个状态常量,这样可以避免在写代码期间维护字符串
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
//定义初始状态
status = PENDING;
//用来保存成功的值
value = null;
//用来保存失败的值
reason = null;
//构造器接收一个执行器方法,会在构造器内部立即执行,然后回传两个回调方法
constructor(executor){
executor(this.resolve,this.reject)
}
//成功的回调
resolve = (val) => {
//这里加判断的原因是因为promise的状态只会被修改一次,成功即成功,失败即失败
if(this.status != PENDING){
return;
}
this.status = FULFILLED;
this.value = val;
}
//失败的回调
reject = (reason) => {
if(this.status != PENDING){
return;
}
this.status = REJECTED;
this.reason = reason;
}
}
来测试一下
const p = new MyPromise((resolve,reject) => {
resolve(123)
})
console.log(p)
下图可以看到promise的状态被修改为成功,而值就是我们传的123
截图贴进来太麻烦了,我们来实现then方法吧
then()
上面介绍过then方法接收两个回调函数为参数,当成功的时候执行第一个,失败反之 明白了原理我们就开搞
//把下面代码粘到class中去
then(onFulfilled,onRjected){
//当前状态为成功的话就去回调第一个方法
if(this.status === FULFILLED){
//回传的值是当前成功的值 resolve()传的
onFulfilled(this.value)
}else if(this.status === REJECTED){
//回传的值是当前失败的值 reject()传的
onRjected(this.reason)
}
}
来测试一下
p.then(res => {
console.log('成功的值是',res)
},reason => {
console.log('失败的值是',reason)
})
//控制台打印 成功的值是123
当前看着结果是没什么问题,但是如果我们resolve被包在了异步里面的话就会导致then方法先执行,执行时当前promise还是PENDING状态,成功和失败的回调都不会被执行 例1:
const p = new MyPromise((resolve,reject) => {
//设置一个定时器,1秒之后修改promise状态
setTimeout(() => {
resolve(123)
}, 1000)
})
p.then(res => {
console.log(res)
},reason => {
console.log(reason)
})
//控制台什么都没有打印,因为错过了判断
来修改一下then\resolve\reject方法,修改后代码如下
//先定义三个状态常量,这样可以避免在写代码期间维护字符串
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
//定义初始状态
status = PENDING;
//用来保存成功的值
value = null;
//用来保存失败的值
reason = null;
//----------新增------------
//成功的回调方法暂存
onFulfilledCb = [];
//失败的回调方法暂存
onRjectedCb = [];
//构造器接收一个执行器方法,会在构造器内部立即执行,然后回传两个回调方法
constructor(executor){
executor(this.resolve,this.reject)
}
//成功的回调
resolve = (val) => {
//这里加判断的原因是因为promise的状态只会被修改一次,成功即成功,失败即失败
if(this.status != PENDING){
return;
}
this.status = FULFILLED;
this.value = val;
//----------新增------------
//如果成功的回调数组中有待执行的方法,取出来执行
if(this.onFulfilledCb.length){
this.onFulfilledCb.forEach(fn => {
fn(val)
})
}
}
//失败的回调
reject = (reason) => {
if(this.status != PENDING){
return;
}
this.status = REJECTED;
this.reason = reason;
//----------新增------------
//如果失败的回调数组中有待执行的方法,取出来执行
if(this.onRjectedCb.length){
this.onRjectedCb.forEach(fn => {
fn(reason)
})
}
}
then(onFulfilled,onRjected){
//当前状态为成功的话就去回调第一个方法
if(this.status === FULFILLED){
//回传的值是当前成功的值 resolve()传的
onFulfilled(this.value)
}else if(this.status === REJECTED){
//回传的值是当前失败的值 reject()传的
onRjected(this.reason)
}else if(this.status === PENDING){//----------新增------------
this.onFulfilledCb.push(onFulfilled)
this.onRjectedCb.push(onRjected)
}
}
}
//再执行上面例1,控制台1秒后输出123
其实then方法是可以链式调用的 比如这样
const p = new Promise((resolve,reject) => {
resolve(123)
})
p.then(res => {
console.log(res)
return 456
},reason => {
console.log(reason)
}).then(res => {
console.log(res)
},reason => {
console.log(reason)
})
//控制台输出123和456
我们来修改一下自己的then方法
then(onFulfilled,onRjected){
return new MyPromise((resolve,reject) => {
//当前状态为成功的话就去回调第一个方法
if(this.status === FULFILLED){
//回传的值是当前成功的值 resolve()传的
//---------修改----------
handleThen(onFulfilled(this.value),resolve,reject)
}else if(this.status === REJECTED){
//回传的值是当前失败的值 reject()传的
//---------修改----------
handleThen(onRjected(this.reason),resolve,reject)
}else if(this.status === PENDING){//----------新增------------
this.onFulfilledCb.push(onFulfilled)
this.onRjectedCb.push(onRjected)
}
})
}
//再来一个处理then方法的方法 写在class外面就可以
function handleThen(val,resolve,reject){ //val是回调方法的返回值,res成功的回调,ren失败的回调
//我们知道then方法能返回普通值也能继续返回一个promise,这个promise的执行结果就是下一个.then的实例
//如果val又是一个promise的话 那它一定有then方法
if(val instanceof MyPromise){
val.then(res => {
resolve(res)
},reason => {
reject(reason)
})
}else{//如果不是promise直接调用成功的回调就可以
resolve(val)
}
}
//测试一下
const p = new MyPromise((resolve,reject) => {
resolve(123)
})
p.then(res => {
console.log(res)
return 456
},reason => {
console.log(reason)
}).then(res => {
console.log(res)
},reason => {
console.log(reason)
})
//控制台同样输出123 456
all()
all方法接收一个promise数组为参数,返回一个promise实例,如果数组中所有promise都为成功状态,则all方法返回成功,若有一个失败则返回失败 all方法的用法如下
const p1 = new Promise((resolve,reject) => {
resolve(1)
})
const p2 = new Promise((resolve,reject) => {
resolve(2)
})
const p3 = new Promise((resolve,reject) => {
resolve(3)
})
const allP = Promise.all([p1,p2,p3])
allP.then(res => {
console.log(res)
},reason => {
console.log(reason)
})
//控制台打印 [1,2,3]
接下来我们来实现一下自己的all方法
//调用all方法时是类名打点调用改的, 所以写成类的静态方法
static all(pArr){
//计数
let num = 0;
//结果数组
let resultArr = [];
return new MyPromise((resolve,reject) => {
pArr.forEach((p,i) => {
p.then(res => {
//进入第一个回调说明当前实例是成功的 num++
num++;
resultArr[i] = res;
//如果num >= 传进来的promise实例 说明所有promise都是成功状态
if(num >= pArr.length){
resolve(resultArr)
}
},reason => {
reject(reason)
})
})
})
}
//测试一下
const p1 = new MyPromise((resolve,reject) => {
resolve(1)
})
const p2 = new MyPromise((resolve,reject) => {
resolve(2)
})
const p3 = new MyPromise((resolve,reject) => {
resolve(3)
})
const allP = MyPromise.all([p1,p2,p3])
allP.then(res => {
console.log(res)
},reason => {
console.log(reason)
})
//控制台同样打印出 [1,2,3]
期约
说一说一开始提到的promise的中文翻译《期约》,这个翻译真的太到位了,并且我觉得非常好听 期约:我在今日与你期许一个约定(resolve,reject),你会在未来拥抱我(then).
总结
以上只是一个promise简单的实现, 我没有看过a+规范,但相信这些应该可以应付面试了,还有好多地方没有写全,比如对错误的处理,还有all方法里面再涉及到异步等... 一门技术我觉得了解它的用法以及主要运行原理就够了,并不是非要手撸出来,卷来卷去卷的是我们自己,金字塔尖上能站多少人呢?
上面代码没有做类型判断,理解和会用就好,我又偷个懒,哈哈~
做人和写代码我都不太喜欢复杂的东西,总想着能不能简单点。
转载自:https://juejin.cn/post/7200344075936333881