一点感悟
Promise 是编写异步的另一种体式格局,鄙人鄙意,它就是 Callback 的一种封装
比拟 Callback ,它有以下特性
- Promise 将异步结果保存起来,能够随时猎取
- 链式挪用 then 要领会返回一个新的 Promise ,从而防止了回调地狱
决议一次异步有两个环节
- 提议异步事宜
- 处置惩罚异步结果
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 范例中采纳的状况机制是 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
正派的 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)
})
})
}
如今来剖析一下,fatherPromise,sonPromise 和 fulPromise 这三者的关联
- 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)