Promise 是编写异步的另外一种方式,鄙人愚见,它就是 Callback 的一种封装git
相比 Callback ,它有如下特色github
决定一次异步有两个环节异步
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 实现同样的效果测试
就像这样ui
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()))
将上述代码封装一下this
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 有点像code
从发布-订阅模式的角度来看:对象
.then(onFulfilled)
来订阅消息,注册处理异步结果的函数resolve(data)
来发布消息,触发处理异步结果的函数去执行,发布的时机是异步事件完成时先前的代码存在一个问题,若是在执行 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 规范中采用的状态机制是 pending
、fulfilled
、rejected
pending
能够转化为 fulfilled
或 rejected
,而且只能转化一次。
转化为 fulfilled
和 rejected
后,状态就不可再变
修改代码以下
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 能够链式调用,从而避免了回调地狱
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 时,会将传入的函数参数 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) }) }) }
如今来分析一下,fatherPromise,sonPromise 和 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)