前言
早上打开掘金无意间发现之前写的一篇文章还没发布,忘记当时是什么原因搁置了。翻看内容有些已经忘记了,正好巩固并完善下,就当作自己的学习记录,下面是手写实现promise以及它的一些方法,加深对其的理解。
以下主要是手写Promise相关方法的简易版本,详细知识可去Promise 对象 - ECMAScript 6入门 (ruanyifeng.com)查看。
一、Promise
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fullfilled
(已成功)和rejected
(已失败)Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果
ini
//定义表示promise对象的3种状态的常量
const PENDING='pending'
const FULFILLED='fullfilled'
const REJECTED='rejected'
function MyPromise(fn){
let _this = this //防止被修改,_this指向调用它的对象
_this.state=PENDING //初始时状态为PENDING(进行中)
_this.value=null //初始时值为空
_this.resolvedCallback=[] //用来装等待执行的fulfilled回调函数
_this.rejectedCallback=[] //用来装等待执行的rejected回调函数
function resolve(value){
if(_this.state===PENDING){//说明状态从pending -> fulfilled
_this.state=FULFILLED //修改状态和值
_this.value=value
_this.resolvedCallback.map(cb=>{ //执行掉resolvedCallback中全部的回调函数
cb(_this.value)
})
}
}
function reject(value){
if(_this.state===PENDING){//说明状态从pending -> rejected
_this.state=REJECTED //修改状态和值
_this.value=value
_this.rejectedCallback.map(cb=>{ //执行掉rejectedCallback中全部的回调函数
cb(_this.value)
})
}
}
try {
fn(resolve,reject)
} catch (error) {
reject(error)
}
}
这样基本上实现了简单的Promise~
1. then方法
then
方法可以接受两个回调函数作为参数。第一个是Promise
对象的状态变为resolved
时调用,第二个是Promise
对象的状态变为rejected
时调用。它们都是可选的。then
方法返回的是一个新的Promise
实例- 为了实现
then
方法的异步,这里借用了setTimeout
实现(不是最优),让里面的函数在状态还未改变时挂起
scss
MyPromise.prototype.then=function(onFulfilled,onRejected) {//then方法接收2个参数
let _this=this //指向调用它的对象
onFulfilled=typeof onFulfilled==='function' ? onFulfilled:c=>c //判断传的参数是否为函数,是的话就不用操作,
//不是的话手动将其改成函数的形式
onRejected=typeof onRejected==='function'?onRejected:r=>{throw r}
return new MyPromise((resolve,reject)=>{//返回Promise实例
if(_this.state===PENDING){//表明此时pending状态,将参数回调函数加到数组中,为了方便下次状态更改的时候可以执行掉之前被阻塞的
_this.resolvedCallback.push(value=>{
setTimeout(()=>{ //用定时器主要是为了模拟then里的方法是异步的
const res=onFulfilled(value)
resolve(res)
})
})
_this.rejectedCallback.push(reason=>{
setTimeout(()=>{
const res=onRejected(reason)
reject(res)
})
})
}
if(_this.state===FULFILLED){//状态更改为fulfilled
setTimeout(()=>{ //用定时器主要是为了模拟then里的方法是异步的
try {
const result=onFulfilled(_this.value)
resolve(result)
} catch (error) {
reject(error)
}
})
}
if(_this.state===REJECTED){
setTimeout(()=>{
try {
const result=onRejected(_this.value)
reject(result)
} catch (error) {
reject(error)
}
})
}
})
}
2. catch方法
Promise.prototype.catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,捕获Promise
的异常错误catch()
方法返回的还是一个 Promise 对象
javascript
MyPromise.prototype.catch=function(onRejected){
let _this = thi
return _this.then(null,onRejected)
}
javascript
//测试
let p1=new MyPromise((resolve, reject)=>{
resolve('ok')
})
p1.then(
(res)=>{
console.log(res);
}
).catch(err=>{
console.log(err);
}) //ok
let p2=new MyPromise((resolve, reject)=>{
reject('error')
})
p2.then(
(res)=>{
console.log(res);
}
).catch(err=>{
console.log(err);
}) //error
3. finally方法
- 不管
Promise
对象最后状态如何,都会执行的操作。也就是finally
与状态无关的,不依赖于 Promise 的执行结果 - 返回
promise
对象 finally
会把前面resolve的值传给后面用
javascript
MyPromise.prototype.MyFinally=function(callback){
let P=this.constructor //确保能够在Promise链中调用正确的resolve和reject方法
return this.then( //this代表指向调用它的对象
value => P.MyResolve(callback()).then(()=>value),
reason => P.MyReject(callback()).then(()=>{throw reason})
)
}
//测试
MyPromise.MyResolve(2)
.then(()=>console.log('resolve'))
.MyFinally(()=>console.log('finally'))
//resolve finally
MyPromise.MyReject(3).MyFinally(()=>console.log('finally')) //finally
let p=new MyPromise((resolve, reject)=>{
resolve('ok')
})
p.then(
(res)=>{
setTimeout(()=>{
console.log(res);
})
return res
}
)
.MyFinally(val=>{
console.log(123);
})
.then((res2)=>{
console.log(res2);
})
//ok
//123
//MyPromise {
// state: 'fullfilled',
// value: 'ok',
// resolvedCallback: [],
// rejectedCallback: []
//}
引入
this.constructor
存放在变量P
的原因:通常情况下,通过this.constructor可以获取到当前Promise实例的构造函数,即Promise类本身。但有时候可能this.constructor
被修改了,不再是原来的Promise类本身,所以才引入这个。
4. Promise.resolve()
- 将现有对象转为
Promise
对象 - 它的参数分多种情况,这里不细说了
javascript
//比较简单,不做过多说明
MyPromise.MyResolve=function(val){
return new MyPromise((resolve, reject) =>{
resolve(val)
})
}
//测试
MyPromise.MyResolve('hello')
.then(res => console.log(res))//hello
5. Promise.reject()
- 返回一个新的
Promise
实例,该实例的状态为rejected
- 参数作为
reject
的理由
javascript
//比较简单,不做过多说明
MyPromise.MyReject=function(val){
return new MyPromise((resolve, reject) =>{
reject(val)
})
}
//测试
MyPromise.MyReject('error')
.then(null,res => console.log(res)) //error
6. Promise.all()
- 返回一个
promise
实例 - 参数接收一个有
iterable
类型的容器,容器中每个元素都是promise
实例,假设这个容器就是数组 - 数组中每个
promise
实例都为fulfilled
状态时,Promise.all()
就fulfilled
状态,并且resolve
的结果为数组中promise实例的返回的resolve
结果,也以数组形式承接 - 如果有一个
promise
实例的状态rejected
,则返回第一个promise
的reject
的reason
javascript
MyPromise.MyAll=function(promises){
return new MyPromise((resolve, reject) =>{ //Promise.all的结果返回一个promise实例
let arr=[],count=0 //用数组来装结果,count计数
promises.forEach((item,i)=>{
item.then(
(res)=>{ //状态fulfilled的添加进数组
arr[i]=res
count++
if(count===promises.length){//如果promises的参数全都fulfilled,则all方法也fulfilled
resolve(arr)
}
},
(reason)=>{ //只有有一个状态rejected的则直接返回此all方法状态也rejected
reject(reason)
return
}
)
})
})
}
//测试 此测试后面都会用到,就不重复写了
const p1 = MyPromise.MyResolve('p1')
const p2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('p2 延时一秒')
}, 1000)
})
const p3 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('p3 延时两秒')
}, 2000)
})
const p4 = MyPromise.MyReject('p4 rejected')
const p5 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject('p5 rejected 延时0.5秒')
}, 500)
})
MyPromise.MyAll([p1, p2, p3])
.then(res => console.log(res)) //[ 'p1', 'p2 延时一秒', 'p3 延时两秒' ]
.catch(err => console.log(err))
由于后面的代码写法差不多,所以后面几个代码就不写备注了
7. Promise.race()
- 将多个
Promise
实例,包装成一个新的Promise
实例 - 返回最先执行结束的
promise
的返回值,无论状态是rejected
还是fulfilled
javascript
MyPromise.MyRace=function(promises){
return new MyPromise((resolve, reject) =>{
for(let item of promises){
item.then( //返回 最先改变状态的
(value)=>{
resolve(value)
},
(reason)=>{
reject(reason)
}
)
}
})
}
//测试 继续上面的测试
MyPromise.MyRace([p2,p3,p5])
.then(res => console.log(res))
.catch(err => console.log(err))//'p5 rejected 延时0.5秒'
8. Promise.allSettled()
- 接受一个数组 作为参数,数组的每个成员都是一个
Promise
对象,并返回一个新的 Promise 对象 - 只有等到参数数组的所有 Promise 对象都发生状态变更,不管是
fulfilled
还是rejected
,返回的promise对象都会发生状态改变 - 返回的对象状态变更后状态总是
fulfilled
,不会变成rejected
javascript
MyPromise.MyAllSettled=function(promises){
let arr=[]
return new MyPromise((resolve, reject) =>{//返回promise实例
let arr=[],count=0
promises.forEach((item,index)=>{
item.then(
value=>{
arr[index]={'status':'fulfilled','value':value}
count++
if(count===promises.length){
resolve(arr)
}
},reason=>{
arr[index]={'status':'rejected','value':reason}
count++
if(count===promises.length){
resolve(arr)
}
})
})
})
}
//测试
MyPromise.MyAllSettled([p2,p3,p5])
.then(res => console.log(res))
.catch(err => console.log(err))
// [
// { status: 'fullfied', value: 'p2 延时一秒' },
// { status: 'fullfied', value: 'p3 延时两秒' },
// { status: 'rejected', value: 'p5 rejected 延时0.5秒' }
// ]
9. Promise.any()
- 接受一组
Promise
实例作为参数,包装成一个新的Promise
实例返回 - 只要参数实例有一个变成
fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态
javascript
MyPromise.MyAny=function(promises){
return new MyPromise((resolve,reject) =>{
let errors=[],count=0
promises.forEach((item,index)=>{
item.then(
value=>{ //只有有一个状态fulfilled则resolve
resolve(value)
return
},
reason=>{
errors[index]=reason
count++
if(count===promises.length){
reject(new AggregateError(errors,'All promises were rejected'))
}
}
)
})
})
}
//测试
const p6 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject('p5 rejected 延时1.5秒')
}, 1500)
})
MyPromise.MyAny([p3,p5])
.then(res => console.log(res)) //'p3 延时两秒'
.catch(err => console.log(err))
MyPromise.MyAny([p5,p6])
.then(res => console.log(res))
.catch(err => console.log(err))
//AggregateError: All promises were rejected
//[errors]: [ 'p5 rejected 延时0.5秒', 'p5 rejected 延时1.5秒' ]
最终版
javascript
const PENDING='pending'
const FULFILLED='fullfilled'
const REJECTED='rejected'
function MyPromise(fn){
let _this = this
_this.state=PENDING
_this.value=null
_this.resolvedCallback=[]
_this.rejectedCallback=[]
function resolve(value){
if(_this.state===PENDING){
_this.state=FULFILLED
_this.value=value
_this.resolvedCallback.map(cb=>{
cb(_this.value)
})
}
}
function reject(value){
if(_this.state===PENDING){
_this.state=REJECTED
_this.value=value
_this.rejectedCallback.map(cb=>{
cb(_this.value)
})
}
}
try {
fn(resolve,reject)
} catch (error) {
reject(error)
}
}
MyPromise.prototype.then=function(onFulfilled,onRejected) {
let _this=this
onFulfilled=typeof onFulfilled==='function' ? onFulfilled:c=>c
onRejected=typeof onRejected==='function'?onRejected:r=>{throw r}
return new MyPromise((resolve,reject)=>{
if(_this.state===PENDING){
_this.resolvedCallback.push(value=>{
setTimeout(()=>{
const res=onFulfilled(value)
resolve(res)
})
})
_this.rejectedCallback.push(reason=>{
setTimeout(()=>{
const res=onRejected(reason)
reject(res)
})
})
}
if(_this.state===FULFILLED){
setTimeout(()=>{
try {
const result=onFulfilled(_this.value)
resolve(result)
} catch (error) {
reject(error)
}
})
}
if(_this.state===REJECTED){
setTimeout(()=>{
try {
const result=onRejected(_this.value)
reject(result)
} catch (error) {
reject(error)
}
})
}
})
}
MyPromise.prototype.catch=function(onRejected){
let _this = this
return _this.then(null,onRejected)
}
MyPromise.MyResolve=function(val){
return new MyPromise((resolve, reject) =>{
resolve(val)
})
}
MyPromise.MyReject=function(val){
return new MyPromise((resolve, reject) =>{
reject(val)
})
}
MyPromise.prototype.MyFinally=function(callback){
let P=this.constructor //确保能够在Promise链中调用正确的resolve和reject方法
return this.then(
value => P.MyResolve(callback()).then(()=>value),
reason => P.MyReject(callback()).then(()=>{throw reason})
)
}
MyPromise.MyAll=function(promises){
return new MyPromise((resolve, reject) =>{
let arr=[],count=0
promises.forEach((item,i)=>{
// console.log(item);
item.then((res)=>{
arr[i]=res
count++
if(count===promises.length){
resolve(arr)
}
},
(reason)=>{
reject(reason)
return
}
)
})
})
}
MyPromise.MyRace=function(promises){
return new MyPromise((resolve, reject) =>{
for(let item of promises){
item.then(
(value)=>{
resolve(value)
},
(reason)=>{
reject(reason)
}
)
}
})
}
MyPromise.MyAllSettled=function(promises){
return new MyPromise((resolve, reject) =>{
let arr=[],count=0
promises.forEach((item,index)=>{
item.then(
value=>{
arr[index]={'status':'fulfilled','value':value}
count++
if(count===promises.length){
resolve(arr)
}
},reason=>{
arr[index]={'status':'rejected','value':reason}
count++
if(count===promises.length){
resolve(arr)
}
})
})
})
}
MyPromise.MyAny=function(promises){
return new MyPromise((resolve,reject) =>{
let errors=[],count=0
promises.forEach((item,index)=>{
item.then(
value=>{
resolve(value)
return
},
reason=>{
errors[index]=reason
count++
if(count===promises.length){
reject(new AggregateError(errors,'All promises were rejected'))
}
}
)
})
})
}
二、应用
- 异步加载图片(用promise,或者定时器加载实现,也就是利用 宏任务 或者 微任务原理)
- 红绿灯算法
加载图片的应用这里就不细讲了,阮一峰老师的 《ECMAScript 6 入门教程》 有写,下面我们来看下红绿灯算法问题。
实现:红灯3秒亮一次,绿灯1秒亮一次,黄灯2秒亮一次,如何让3个灯不断交替重复亮
答案:
scss
function red() {
console.log('red');
}
function green() {
console.log('green');
}
function yellow() {
console.log('yellow');
}
//封装一个方法
function light(cb,time){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
cb()
resolve()
},time)
})
}
const step=function(){
Promise.resolve()
.then(()=>{
return light(red,3000)
})
.then(()=>{
return light(green,1000)
})
.then(()=>{
return light(yellow,2000)
})
.then(()=>{
step()
})
}
//为了实现题目要求的效果,利用Promise实现,并且在 3s后红灯亮,此时再等1s后绿灯亮,绿灯亮后
//再等2s黄灯亮,如此反复。那么就在每个then 后return 值并且这个值是promise对象
//那么then里的操作会等前面要返回的promise值fulfilled再执行
step() // red green yellow 交替执行打印
注:
- 当第1个
then
没有人为返回一个Promise
对象时,那就默认返回resolved
状态。那么第2个then
也会默认执行- 当第1个
then
有人为返回一个Promise
对象时,那么第2个then
的执行就得依据第一个then
的状态执行
结束语
本篇文章就到此为止啦,由于本人经验水平有限,难免会有纰漏,对此欢迎指正 。如觉得本文对你有帮助的话,欢迎点赞收藏❤❤❤。要是您觉得有更好的方法,欢迎评论,提出建议!