Promise源码

421 阅读11分钟

初始化项目

1.生成package.json

运行命令,生成package.json文件

npm init -y

2.安装rollup,配置rollup.config.js

import commonjs from 'rollup-plugin-commonjs'

export default {
    input:'./src/core.js',
    output:{
        file:'./dist/index.js',
        format:'umd',
        name:'myPromise'
    },
    plugins:[
        commonjs()
    ]
}

package.json添加命令:

"build": "rollup -c -w",

3.初始目录结构

image-20220228230053185.png

Promise的状态和then方法

性质

  • 当使用Promise的时候会传入一个执行器,这个执行器会立即执行,而且可能会出错,需要用try...catch捕获这个错误
  • executor函数接受两个函数,resolve和reject来改变promise的状态
  • resolve和reject接受参数来改变promise的值(既改变状态也改变值)
  • Promise的then方法是异步的
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class myPromise {
    constructor(executor) {
        this.status = PENDING // promise默认的状态
        this.value = undefined  // 成功的原因
        this.reason = undefined  // 失败的原因

        const resolve = (value) => {
            if (this.status === PENDING) { // 只有在PENDING状态时才改变状态和值
                this.value = value
                this.status = FULFILLED
            }
        }

        const reject = (reason) => {
            if (this.status === PENDING) { // 只有在PENDING状态时才改变状态和值
                this.reason = reason
                this.status = REJECTED
            }
        }

        try {
            executor(resolve, reject)
        } catch (e) {
            reject(e) // 一旦抛出异常 走reject

        }
    }
    then(onFulFilled,onRejected) {  // 成功的回调 和 失败的回调
        if(this.status === FULFILLED){
            onFulFilled(this.value) // 把成功的值传给成功回调
        }
        if(this.status === REJECTED){
            onRejected(this.reason) // 把失败的原因传给失败的回调
        }
    }
}

module.exports = myPromise

DEMO

let a = new myPromise((resolve, reject) => {
    console.log('gsdmypromise')
    resolve('gsda') // 走resolve改变了状态之后不会再走reject
    reject('gsdb')
}).then(result => {
    console.log('gsdresult', result) // 这里的result就是上面resolve里的参数gsda
}, reason => {
    console.log('gsdreason', reason)
})

微信图片_20220502101611.png

【注意】executor接受的resolve和reject只是用来更改primise实例的状态和值,而then方法接受的两个参数是回调函数,根据promise实例状态的不同调用不同的回调函数

存储成功或者失败的回调

当用户调用then方法的时候,promise实例状态可能还是为pending,需要把成功回调或者失败回调暂存起来,等resolve或者reject改变实例状态后,在触发对应的成功或者失败回调

例如下面例子:

let a = new myPromise((resolve, reject) => { // 此时状态为PENDING
    setTimeout(() => {
        resolve('gsda')
    }, 1000);
}).then(result => {  // 此时走then方法时,promise状态为PENDING,1秒后才修改为FULFILLED
    console.log('gsdresult', result) 
}, reason => {
    console.log('gsdreason', reason)
})
console.log('then方法返回的promise',a)

因此对前面的源码进行优化

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class myPromise {
    constructor(executor) {
        this.status = PENDING 
        this.value = undefined  
        this.reason = undefined  
        this.onResolvedCallbacks = []  // 存放成功的回调
        this.onRejectedCallbacks = []  // 存放失败的回调
 
        const resolve = (value) => {
            if (this.status === PENDING) { 
                this.value = value
                this.status = FULFILLED
                this.onResolvedCallbacks.forEach(fn=>fn()) // 发布模式
            }
        }

        const reject = (reason) => {
            if (this.status === PENDING) { 
                this.reason = reason
                this.status = REJECTED
                this.onRejectedCallbacks.forEach(fn=>fn()) // 发布模式
            }
        }

        try {
            executor(resolve, reject)
        } catch (e) {
            reject(e)
        }
    }
    then(onFulFilled,onRejected) {  
        if(this.status === FULFILLED){
            onFulFilled(this.value) 
        }
        if(this.status === REJECTED){
            onRejected(this.reason) 
        }
        if(this.status === PENDING){ // 代码是异步调用resolve或者reject 订阅模式
        // 不直接push onRejected(this.reason)或 onFulFilled(this.value)目的是可以加入自己的逻辑
            this.onRejectedCallbacks.push(()=>{
                // do something
                onRejected(this.reason) 
            }) 
            this.onResolvedCallbacks.push(()=>{
                // do something
                onFulFilled(this.value) 
            })         
        }
    }
}

module.exports = myPromise

链式调用

promise核心

  • 用链式调用解决了回调地域问题

  • 同步并发问题,有多个并发的请求,用promise来同步它们的结果

  • 多个异步处理错误问题,没有promise之前每个回调函数都要单独处理错误

promise的then方法返回值处理

当调用then方法后会返回一个新的promise

// 原生Promise链式调用特点
let promise = new Promise((resolve,reject)=>{
     resolve('hello')
})

// 情况1:通过return方式传递结果,传递普通值,可以传递到下一个then方法里的成功回调函数
promise.then((res)=>{
  return res
})
.then((res)=>{
  console.log(res) // hello
})

// 情况2:通过新的Promise resovle结果,传递到下一个then方法的成功回调
promise.then((res)=>{
 return res
})
.then((res)=>{
   return new Promise((resolve,reject)=>{
     setTimeout(()=>{
         resolve(res)
     },2000)
})
.then((res)=>{
  console.log(res) // hello
})

// 情况3:通过新的Promise reject结果,传递到下一个then方法的失败回调
 promise.then((res)=>{
  return res
 })
 .then((res)=>{
    return new Promise((resolve,reject)=>{
      setTimeout(()=>{
          reject('error')
      },2000)
 })
 .then((res)=>{
   console.log(res)
 },(err)=>{
   console.log(err) // error
 })

// 情况4:then走了失败的回调,再调用then,走成功的回调,但是值为undefined
 promise.then((res)=>{
  return res
 })
 .then((res)=>{
    return new Promise((resolve,reject)=>{
      setTimeout(()=>{
          reject('error')
      },2000)
 })
 .then((res)=>{
   console.log(res)
 },(err)=>{
   console.log(err) // error 默认return undefined
 })
 .then((res)=>{
    console.log(res)  //undefined
 })

// 情况5:如果then中用throw ,那么下一次then走失败的回调,值为抛出的错误
 promise.then((res)=>{
  return res
 })
 .then((res)=>{
    return new Promise((resolve,reject)=>{
      setTimeout(()=>{
         reject('error')
     },2000)
})
.then((res)=>{
  console.log(res)
},(err)=>{
  console.log(err) // error 默认return undefined
})
.then(()=>{
   throw new Error('throw error')
})
.then((res)=>{
   console.log(res)
 },(err)=>{
   console.log(err) // throw error
})

// 情况6:catch相当于then,遵循then的运行原则,可以捕获异常
// 如果上一个then里面return失败的条件,那么下一个then走失败的回调,但是如果下一个then没有定义失败的回调,那么走catch
// 如果下一个then定义了失败的回调,那么就不走catch
// catch后面还可以调用then,catch返回的是一个新的promise实例,执行的是then方法的第一个回调函数

总结

  • 成功的条件

    • then里面return 普通的值

    • then里面return 新的Promise的成功的结果

无论上一次then走成功回调还是失败回调,只要返回值是普通值或者新的Promise的成功结果,下一次then走成功回调

  • 失败的条件

    • then里面return 新的Promise的失败的原因

    • then里面抛出异常 throw new Error

无论上一次then走成功回调还是失败回调,只要返回值是抛出异常或新的Promise的失败结果,下一次then走失败回调

then返回一个普通值

无论上一个then走失败的回调还是成功的回调,只要它返回一个普通值,那么下次then走成功的回调

如果上一个then抛出错误,下次then走失败的回调

下面的x就是成功回调或者失败回调执行的结果,如果x是一个普通值的时候

then(onFulFilled, onRejected) {
    // 每次调用then方法返回一个新的promise 用于实现链式调用
    let promise2 = new myPromise((resolve, reject) => {
        if (this.status === FULFILLED) {
            try {
                let x = onFulFilled(this.value)
                resolve(x)
            } catch (error) { 
                reject(error)
            }
        }
        if (this.status === REJECTED) {
            try {
                let x = onRejected(this.reason)
                resolve(x)
            } catch (error) {
                reject(error)
            }
        }
        if (this.status === PENDING) { 
            this.onRejectedCallbacks.push(() => {
                // do something
                try {
                    let x = onRejected(this.reason)
                    resolve(x)
                } catch (error) {
                    reject(error)

                }
            })
            this.onResolvedCallbacks.push(() => {
                try {
                    let x = onFulFilled(this.value)
                    resolve(x)
                } catch (error) {
                    reject(error)
                }
            })
        }
    })
    return promise2
}

then返回一个promise

需要看promise的状态是成功还是失败,如果成功调用promise2的resolve,如果失败调用promise2的reject

定义一个函数resolvePromise,根据x的值来判断调用promise2的resolve还是reject

用定时器包裹的原因:promise属于微任务,settimeout属于宏任务,当微任务执行完毕才会执行宏任务,这样resolvePromise就能够获取到promise2的实例

对then方法进行改造

then(onFulFilled, onRejected) {
    let promise2 = new myPromise((resolve, reject) => {
        if (this.status === FULFILLED) {
            setTimeout(() => {
                try {
                    let x = onFulFilled(this.value)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (error) {
                    reject(error)
                }
            }, 0);
        }
        if (this.status === REJECTED) {
            setTimeout(() => {
                try {
                    let x = onRejected(this.reason)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (error) {
                    reject(error)
                }
            }, 0);
        }
        if (this.status === PENDING) { 
            this.onRejectedCallbacks.push(() => {
                // do something
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                }, 0);
            })
            this.onResolvedCallbacks.push(() => {
                setTimeout(() => {
                    try {
                        let x = onFulFilled(this.value)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                }, 0);
            })
        }
    })
    return promise2
}

构造一个resolvePromise函数

function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise'))
    }

    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        let called = false // 保证改变状态后不可逆
        // 有可能then方法是通过defineProperty实现的,取值时会发生异常,当发生异常的时候直接调用reject
        try { 
            let then = x.then
            if (typeof then === 'function') {
           // 如果then是一个函数,那么认为x是一个promise,不写成x.then()是避免触发getter的时候发生异常
                then.call(x, (y) => {
                    if (called) return
                    called = true
                    // 嵌套promise的情形,直到解析值为普通值为止
                    resolvePromise(promise2, y, resolve, reject) 
                }, (r) => {
                    if (called) return
                    called = true
                    reject(r)
                })
            } else {
                resolve(x) // 如果then不是函数,那么认为x是一个普通值,直接调用resolve
            }
        } catch (err) {
            if (called) return
            called = true
            reject(err)
        }
    } else {
        // x是一个普通值
        resolve(x)
    }
}

DEMO

promise嵌套: 如果then方法return一个promise,这个promise的resolve方法里再传入一个promise

let promise = new myPromise((resolve, reject) => {
    resolve(123)
}).then(result => {
        return new myPromise((resolve, reject) => {
            setTimeout(() => {
                resolve(new myPromise((resolve,reject)=>{
                    setTimeout(() => {
                        resolve(200)
                    }, 1000);
                }))
                // resolve(200)
            }, 1000);
        })
    }, reason => {
        console.log('error', reason)
    })
promise.then(data => {
    console.log('data', data)
}, err => {
    console.log('err', err)
})

// data 200

then方法参数可选

then方法的成功回调和失败回调是可选参数,如果不传默认设置为一个函数,这样一来可以实现穿透

then(onFulFilled, onRejected) {
    onFulFilled = typeof onFulFilled === 'function' ? onFulFilled: v=>v
    onRejected = typeof onRejected === 'function' ? onRejected : err=> {throw err}
    ...
}

DEMO

let promise= new myPromise((resolve,reject)=>{
    reject(200)
}).then().then().then().then(
    data=>{
        console.log(data)
    },
    err=>{
        console.log('err',err)
    }
)
// err 200

测试和Promise延迟对象的使用

利用promises-aplus-test来测试

npm install promises-aplus-tests 

配置script命令

"scripts": {
  "build": "rollup -c -w",
  "test": "promises-aplus-tests src/core.js"
},

然后在源码文件下写入

// 延迟对象,帮助减少一次套用 
// dfd是一个对象 对象上创造一个promise
myPromise.deferred = function(){
    let dfd = {}
    dfd.promise = new myPromise((resolve,reject)=>{
        dfd.resolve = resolve
        dfd.reject = reject
    })
    return dfd
}

// 【补充】延迟对象的应用,正常封装一个readFile
function readFile (filePath,encoding){
    return new Promise((resolve,reject)=>{
        fs.readFile(filePath,encoding,(err,data)=>{
            if(err) return reject(err)
            resolve(data)
        })

    })
}

// 使用延迟对象
function readFile(filePath, encoding) {
    let dfd = myPromise.deferred()
    fs.readFile(filePath, encoding, (err, data) => {
        if (err) return dfd.reject(err)
        dfd.resolve(data)
    })
    return dfd.promise
}

使用命令npm run test

image-20220311001825922.png

一个promise直接resolve一个promise

源码实现

const resolve = (value) => {
    // 如果resolve里直接嵌套一个promise,会等待里面promise执行完毕
    if(value instanceof myPromise){  
        return value.then(resolve,reject) 
    }
    if (this.status === PENDING) {
        this.value = value
        this.status = FULFILLED
        this.onResolvedCallbacks.forEach(fn => fn())
    }
}

DEMO

new myPromise((resolve,reject)=>{
    resolve(new myPromise((resolve,reject)=>{
        resolve(100)
    }))
}).then(data=>{
    console.log(data)
})

// 100

静态方法

直接通过类来调用

resolve

直接返回一个状态为成功的Promise对象

【补充】如果resolve接受一个promise,它会把等待这个promise执行完毕

源码实现

static resolve(value){
    return new myPromise((resolve,reject)=>{
        resolve(value)
    })
}

DEMO

myPromise.resolve(new myPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(1000)
    }, 1000);
})).then(data=>{
    console.log(data)
})

// 1000

reject

直接返回一个状态为失败的Promise对象

源码实现

static reject(reason){
    return new myPromise((resolve,reject)=>{
        reject(reason)
    })
}

DEMO

myPromise.reject(new myPromise((resolve, reject) => {
    setTimeout(() => {
        resolve(1000)
    }, 1000);
})).then(data => {
    console.log(data)
}, err => {
    console.log('err', err)
})

当一个promise直接reject一个promise的时候,并不会等待里面的promise执行完毕,而是把这个promise直接返回,在then方法的失败回调中当作参数接受这个promise,所以上述会打印一个promise

image-20220315232605620.png

all

接受一个数组,这个数组的可以是由普通值或者promise组成,当全部成功的时候返回所有成功的结果组成的数组,只要有一项失败,那么就返回失败的结果

源码实现

static all(promises) {
    return new myPromise((resolve, reject) => {
        let res = []
        let times = 0 // 计算成功的个数

        const handleSuccess = (index, val) => {
            res[index] = val
            if(++times === promises.length){
                resolve(res)
            }
        }

        for (let i = 0; i < promises.length; i++) {
            // 判断是否是promises,如果是调用then方法获取结果
            let p = promises[i]
            if (p.then && typeof p.then === 'function') {
                p.then((data) => {
                    handleSuccess(i,data)
                }, reject) // 如果某一个promise失败了 直接走失败即可
            }else{
            // 如果不是promise,直接把坐标和数据传入
                handleSuccess(i,p) 
            }
        }
    })
}

DEMO

myPromise.all([1,2,3,new myPromise((resolve,reject)=>{
    setTimeout(() => {
        resolve('1')
    }, 1000);
}),new myPromise((resolve,reject)=>{
    setTimeout(() => {
        reject('2')
    }, 1000);
})]).then(data=>{
    console.log(data)
},err=>{
    console.log('err',err)
})
// err 2
myPromise.all([1,2,3,new myPromise((resolve,reject)=>{
    setTimeout(() => {
        resolve('1')
    }, 1000);
}),new myPromise((resolve,reject)=>{
    setTimeout(() => {
        resolve('2')
    }, 1000);
})]).then(data=>{
    console.log(data)
},err=>{
    console.log('err',err)
})

//  [1, 2, 3, '1', '2']

race

接受一个数组,数组的每一项可以是promise或者普通值,如果数组中的某项更快获取到结果,那么race采用那个结果

【注意】race只是会采用最快获取的结果,但不阻碍数组中其他项的代码的执行,

static race(promises){
    return new myPromise((resolve,reject)=>{
        for(let i = 0 ; i < promises.length ; i++){
            let p = promises[i]
            if(p && typeof p.then === 'function'){
                p.then(resolve,reject) // 一旦成功就直接 停止
            }else{
                resolve(p)
            }
        }
    })
}

DEMO

myPromise.race([new myPromise((resolve,reject)=>{
    setTimeout(() => {
        resolve('1')
    }, 5000);
}),new myPromise((resolve,reject)=>{
    setTimeout(() => {
        resolve('2')
    }, 2000);
})]).then(data=>{
    console.log(data)
},err=>{
    console.log('err',err)
})

// 2 

应用场景

超时处理,例如图片加载的时候如果请求时间过长,就不采用成功的结果

// 自己构造一个promise,暴露一个中断方法
function wrap(p1){
    let abort // 把abort暴露出去,用户可以调用abort把p的状态改为reject,等价于p2失败
    let p = new Promise((resolve,reject)=>{
        abort = reject
    })
    let p2 =  Promise.race([p1,p])
    p2.abort = abort
    return p2
}

原型方法

catch

catch(errFn){
    return this.then(null,errFn)
}

finally

无论成功还是失败都会执行,但是可以向下执行,相当于then方法

  • finally可以return一个promise,但是它不会采用这个promise成功的结果,但是如果这个promise状态为失败,会被下一个then方法的失败回调捕获
  • finally不接受参数,即使传参也会显示undefined

以原生Promise为例子

let p1 = new Promise((resolve,reject)=>{
    setTimeout(() => {
        reject('失败')
    }, 3000);
}).finally((data)=>{
    console.log('finally',data)
    // finally return一个promise 不采用这个promise成功的结果
    return new Promise((resolve,reject)=>{
        setTimeout(() => {
            resolve(1000)
        }, 1000);
    })
}).then(data=>{
    console.log('data',data)
}).catch(e=>{
    console.log('catch',e)
})

image-20220316233034047.png

但是如果finally返回的promise状态失败了,那么就会抛出错误

let p1 = new Promise((resolve,reject)=>{
    setTimeout(() => {
        reject('失败')
    }, 3000);
}).finally((data)=>{
    console.log('finally',data)
    // finally return一个promise 不采用这个promise成功的结果,=
    return new Promise((resolve,reject)=>{
        setTimeout(() => {
            reject(1000)
        }, 1000);
    })
}).then(data=>{
    console.log('data',data)
}).catch(e=>{
    console.log('catch',e)
})

image-20220316233309814.png

源码实现

finally(cb){
    // 这个data是上一次成功的结果
    return this.then((data)=>{
        // 如果cb返回的是一个promise,要等待这个promise执行完毕,所以用resolve方法包裹
        // 并且把上一次成功的结果往下传递
        // 并没有把cb执行完成功的结果n往下传递 
        return myPromise.resolve(cb()).then(()=>data)
    },(err)=>{
        // 上一个失败的结果往下传递
        // 如果cb返回的是一个promise,如果这个promise状态为失败,就不走then方法向外抛出上一个失败的结果
        // 而是直接reject,后面的catch或者下一个then的失败回调会捕获到这个错误
        return myPromise.resolve(cb()).then(()=>{throw err})
    })
}

Promisfy

将一个异步的方法转化成promise的形式,主要是给node来使用的,回调函数的参数第一个永远是err

function promisify(readFile){
    return function(...args){
        return new Promise((reslove,reject)=>{
            readFile(...args,(err,data)=>{
                if(err)reject(err)
                reslove(data)
            })
        })
    }
}

应用

const fs = require('fs')
let readFile = promisify(fs.readFile)
readFile('./src/code.js','utf8').then(data=>{
    console.log(data)
})