山寨一个 Promise

一点感悟

Promise 是编写异步的另一种体式格局,鄙人鄙意,它就是 Callback 的一种封装

比拟 Callback ,它有以下特性

  • Promise 将异步结果保存起来,能够随时猎取
  • 链式挪用 then 要领会返回一个新的 Promise ,从而防止了回调地狱

决议一次异步有两个环节

  1. 提议异步事宜
  2. 处置惩罚异步结果

Promise 能够给一个异步事宜注册多个处置惩罚函数,举个栗子,就像如许

let p1 = new Promise((resolve) => {
  fs.readFile('./test.js', 'utf8', (err, data) => {
    resolve(data)
  })
})
p1.then(data => console.log(data))
p1.then(data => console.log(data.toUpperCase()))

用 Callback 完成一样的结果

  • 用 callbacks 将一切注册的函数保存
  • 待异步事宜返回结果,再遍历 callbacks ,顺次实行一切注册的函数

就像如许

let callbacks = []
function resolve(data){
  callbacks.forEach(cb => cb(data))
}

fs.readFile('./test.js', 'utf8', (err, data) => {
  resolve(data)
})

callbacks.push(data => console.log(data))
callbacks.push(data => console.log(data.toUpperCase()))

将上述代码封装一下

const fs = require('fs')

class FakePromise {
  constructor(fn){
      this.callbacks = []
      resolve = resolve.bind(this)
    function resolve(data){
      this.callbacks.forEach(cb => cb(data))
    }
    fn(resolve)
  }
  
  then(onFulfilled){
    this.callbacks.push(onFulfilled)
  }
}

let p1 = new FakePromise(resolve => {
  fs.readFile('./test.js', 'utf8', (err, data) => {
    resolve(data)
  })
})
p1.then(data => console.log(data))
p1.then(data => console.log(data.toUpperCase()))

哈?是不是是和真的 Promise 有点像

从宣布-定阅形式的角度来看:

  • FakePromise 中经由过程 .then(onFulfilled) 来定阅音讯,注册处置惩罚异步结果的函数
  • 经由过程 resolve(data) 来宣布音讯,触发处置惩罚异步结果的函数去实行,宣布的机遇是异步事宜完成时

延时 resolve

先前的代码存在一个题目,假如在实行 p1.then(data => console.log(data)) 之前,resolve(data) 就已实行了,那末再经由过程 .then(onFulfilled) 注册的处置惩罚异步结果的函数将永久不会实行

为了防止这类状况,革新 resolve 函数,在其内部增加 setTimeout,从而保证那些注册的处置惩罚函数是鄙人一个事宜行列中实行,就像如许

function resolve(value) {
    setTimeout(() => {
        this.callbacks.forEach(cb => cb(value))
    }, 0)
}

经由过程延时实行 resolve 内部的函数,保证了先定阅音讯,再宣布音讯

然则 Promise 另有个分外的功用是在宣布音讯后,依旧能够定阅音讯,而且马上实行,就像如许

const fs = require('fs')

let p1 = new Promise(resolve => {
    fs.readFile('./test.js', 'utf8', (err, data) => resolve(data))
})

p1.then(data => console.log(data))
setTimeout(function(){
    p1.then(data => console.log(data.toUpperCase()))
}, 5000)

5s以内,文件早已读取胜利,然则在5s以后,依旧能够经由过程 .then 注册处置惩罚事宜,而且该事宜会马上实行

先宣布,再定阅

完成先宣布,再定阅的基本是将音讯保存下来。其次要纪录状况,推断音讯是不是已被宣布,假如未宣布音讯,则经由过程 .then 来注册回调时,是将回调函数增加到内部的回调行列中;假如音讯已宣布,则经由过程 .then 来注册回调时,直接将音讯传至回调函数,并实行

Promise 范例中采纳的状况机制是 pendingfulfilledrejected

pending 能够转化为 fulfilledrejected ,而且只能转化一次。

转化为 fulfilledrejected 后,状况就不可再变

修正代码以下

class FakePromise {
    constructor(fn) {
        this.value = null
        this.state = 'pending'
        this.callbacks = []
        resolve = resolve.bind(this)

        function resolve(value) {
            setTimeout(() => {
                this.value = value
                this.state = 'fulfilled'
                this.callbacks.forEach(cb => cb(value))
            }, 0)
        }
        fn(resolve)
    }

    then(onFulfilled) {
        if (this.state === 'pending') {
            this.callbacks.push(onFulfilled)
        } else {
            onFulfilled(this.value)
        }
    }
}

既然完成了先宣布,再定阅,那末 resolve 中的 setTimeout 是不是是能够去掉了?

并不能够,由于人家正派的 Promise 是如许的

let p1 = new Promise(resolve => {
    resolve('haha')
})
p1.then(data => console.log(data))
p1.then(data => console.log(data.toUpperCase()))
console.log('xixi')
// xixi
// haha
// HAHA

只要保存 resolve 中 setTimeout 才能使 FakePromise 完成雷同的结果

let p1 = new FakePromise(resolve => {
    resolve('haha')
})
p1.then(data => console.log(data))
p1.then(data => console.log(data.toUpperCase()))
console.log('xixi')
// xixi
// haha
// HAHA

没有 setTimeout 的输出结果

// haha
// HAHA
// xixi

链式 Promise

正派的 Promise 能够链式挪用,从而防止了回调地狱

let p1 = new Promise(resolve => {
    fs.readFile('./test.js', 'utf8', (err, data) => {
        resolve(data)
    })
}).then(res => {
    return new Promise(resolve => {
        fs.readFile('./main.js', 'utf8', (err, data) => {
            resolve(data)
        })
    })
}).then(res => {
    console.log(res)
})

正派的 Promise 挪用 then 要领会返回一个新的 Promise 对象

我们捏造的 FakePromise 并没有完成这一功用,本来的 then 要领

...
    then(onFulfilled){
        if (this.state === 'pending') {
            this.callbacks.push(onFulfilled)
        } else {
            onFulfilled(this.value)
        }
    }
...

本来的 then 要领就是依据 state 推断是注册 onFulfilled 函数,照样实行 onFulfilled 函数

为了完成 FakePromise 的高仿,我们要革新 then 要领,使其返回一个新的 FakePromise ,为了轻易辨别,将返回的 FakePromise 取名为 SonFakePromise ,而先前挪用 then 的对象为 FatherFakePromise

那末题目来了

  • 那末组织这个 SonFakePromise 的函数参数是什么
  • 这个 SonFakePromise 什么时刻 resolve ?

起首,当组织一个新的 SonFakePromise 时,会将传入的函数参数 fn 实行一遍,且这个函数有 resolve 参数

...
    then(onFulfilled){
      if(this.state === 'pending'){
        this.callbacks.push(onFulfilled)
        let SonFakePromise = new FakePromise(function fn(resolve){
          
        })
        return SonFakePromise
      }else{
        onFulfilled(this.value)
        let SonFakePromise = new FakePromise(function fn(resolve){
          
        })
        return SonFakePromise
      }
    }
...

如今的题目是这个 SonFakePromise 什么时刻 resolve ?即组织函数中的函数参数 fn 怎样定义

连系正派 Promise 的例子来看

let faherPromise = new Promise(resolve => {
    fs.readFile('./test.js', 'utf8', (err, data) => {
        resolve(data)
    })
}).then(res => {
    return new Promise(resolve => {
        fs.readFile('./main.js', 'utf8', (err, data) => {
            resolve(data)
        })
    })
}).then(res => {
    console.log(res)
})
// 等同于
let faherPromise = new Promise(resolve => {
    fs.readFile('./test.js', 'utf8', (err, data) => {
        resolve(data)
    })
})
let sonPromise = faherPromise.then(function onFulfilled(res){
    return new Promise(function fn(resolve){
        fs.readFile('./main.js', 'utf8', (err, data) => {
            resolve(data)
        })
    })
}).then(res => {
    console.log(res)
})

在例子中,onFulfilled 函数以下,且其实行后返回一个新的 Promise,临时取名为 fulPromise

function onFulfilled(res) {
  return new Promise(function fn(resolve){
    fs.readFile('./main.js', 'utf8', (err, data) => {
      resolve(data)
    })
  })
}

如今来剖析一下,fatherPromisesonPromisefulPromise 这三者的关联

  • sonPromise 是挪用 fatherPromise 的 then 要领返回的
  • 而挪用这个 then 要领须要传入一个函数参数,取名为 retFulPromise
  • retFulPromise 函数实行的返回值 fulPromise

愿望下面的代码能有助于邃晓

let fatherPromise = new Promise(function fatherFn(fatherResolve){
  fs.readFile('./test.js', 'utf8', (err, data) => {
    fatherResolve(data)
  })
})

let sonPromise = fatherPromise.then(retFulPromise)

function retFulPromise(res) {
  let fulPromise = new Promise(function fulFn(fulResolve){
    fs.readFile('./main.js', 'utf8', (err, data) => {
      fulResolve(data)
    })
  })
  return fulPromise
}

fatherPromise 的状况为 fulfilled 时,会实行 retFulPromise,其返回 fulPromise ,当这个 fulPromise 实行 fulResolve 时,即完成读取 main.js 时, sonPromise 也会实行内部的 resolve

所以能够算作,sonPromise 的 sonResolve 函数,也被注册到了 fulPromise 上

So,了解了全部流程,该怎样修正本身的 FakePromise 呢?

秀操纵,磨练技能的时刻到了,将 sonResolve 的援用保存起来,注册到 fulFakePromise 上

const fs = require('fs')

class FakePromise {
    constructor(fn) {
        this.value = null
        this.state = 'pending'
        this.callbacks = []
        resolve = resolve.bind(this)

        function resolve(value) {
            setTimeout(() => {
                this.value = value
                this.state = 'fulfilled'
                this.callbacks.forEach(cb => {
                    let returnValue = cb.onFulfilled(value)
                    if (returnValue instanceof FakePromise) {
                        returnValue.then(cb.sonResolveRes)
                    }
                })
            })
        }
        fn(resolve)
    }

    then(onFulfilled) {
        if (this.state === 'pending') {
            let sonResolveRes = null
            let sonFakePromise = new FakePromise(function sonFn(sonResolve) {
                sonResolveRes = sonResolve
            })
            this.callbacks.push({
                sonFakePromise,
                sonResolveRes,
                onFulfilled
            })
            return sonFakePromise
        } else {
            let value = onFulfilled(this.value)
            let sonResolveRes = null
            let sonFakePromise = new FakePromise(function sonFn(sonResolve) {
                sonResolveRes = sonResolve
            })
            if (value instanceof FakePromise) {
                value.then(sonResolveRes)
            }
            return sonFakePromise
        }
    }
}

多角度测试

let fatherFakePromise = new FakePromise(resolve => {
    fs.readFile('./test.js', 'utf8', (err, data) => {
        resolve(data)
    })
})
let sonFakePromise = fatherFakePromise.then(function onFulfilled(res) {
    return new FakePromise(function fn(resolve) {
        fs.readFile('./main.js', 'utf8', (err, data) => {
            resolve(data)
        })
    })
}).then(res => {
    console.log(res)
})
let fatherFakePromise = new FakePromise(resolve => {
    fs.readFile('./test.js', 'utf8', (err, data) => {
        resolve(data)
    })
})
setTimeout(function () {
    let sonFakePromise = fatherFakePromise.then(function onFulfilled(res) {
        return new FakePromise(function fn(resolve) {
            fs.readFile('./main.js', 'utf8', (err, data) => {
                resolve(data)
            })
        })
    }).then(res => {
        console.log(res)
    })
}, 1000)
let fatherFakePromise = new FakePromise(resolve => {
    resolve('haha')
})

let sonFakePromise = fatherFakePromise.then(function onFulfilled(res) {
    return new FakePromise(function fn(resolve) {
        fs.readFile('./main.js', 'utf8', (err, data) => {
            resolve(data)
        })
    })
}).then(res => {
    console.log(res)
})
let fatherFakePromise = new FakePromise(resolve => {
    resolve('haha')
})

setTimeout(function () {
    let sonFakePromise = fatherFakePromise.then(function onFulfilled(res) {
        return new FakePromise(function fn(resolve) {
            fs.readFile('./main.js', 'utf8', (err, data) => {
                resolve(data)
            })
        })
    }).then(res => {
        console.log(res)
    })
}, 1000)

参考资料

    原文作者:nbb3210
    原文地址: https://segmentfault.com/a/1190000013141568
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞