# 实现符合A+规范的Promise
# 实现简易版的Promise
我们首先来实现一个简单版的Promise
,让他支持下面的简单用法
const p = new Promise((resolve, reject) => {
console.log('executor')
resolve('成功了')
})
p.then((value) => {
console.log('状态是' + value)
}, (reason) => {
console.log('状态是' + reason)
})
TIP
这里需要注意一些promise的一些简单特性,首先new Promise()
里面的是同步代码,会立即执行
其次promise
的状态一旦变为成功或者失败状态就不可以被改变了
所以上诉输出的结构是 executor
状态是成功了
接下来我们实现一款最简单的Promise
const SUCCESS = 'fulfilled' // 成功的状态
const FALL = 'rejected' // 失败的状态
const PENDING = 'pending' // 等待的状态
class Promisse {
constructor (executor) {
this.status = PENDING
this.value = undefined // 存放当前成功的值
this.reason = undefined // 存放当前失败的值
const resolve = (value) => {
if (this.status === PENDING) {
this.status = SUCCESS
this.value = value
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.status = FALL
this.reason = reason
}
}
try { // 如果执行器的同步代码报错,进行捕获处理
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFullFilled, onRejected) {
if (this.status === SUCCESS) {
onFullFilled(this.value)
}
if (this.status === FALL) {
onRejected(this.reason)
}
}
}
module.exports = Promisse
首先我们定义三个表示状态的常量,其次我们执行器executor
对应我们new Promise(xxx)
传入的xxx函数,他接收2个参数resolve
和reject
,这2个方法的作用是缓存当前成功的状态或者失败的状态,并且修改对应的状态为成功或失败
然后我们需要注意的是executor
执行的时候需要用try-catch
包裹起来,因为同步代码也有可能报错
最后我们需要写一个then
方法,他可以根据当前promise
的状态执行对应的回调函数
# 支持异步调用
上述最简单的promise
有一个小缺陷,就是不支持异步操作,如
let Promise = require('./promise1')
const fs = require('fs')
const p = new Promise((resolve, reject) => {
fs.readFile('./name.txt', 'utf8', function(err, data) {
if (err) {
reject(err)
}
resolve(data)
})
})
p.then((value) => {
console.log(value)
}, (reason) => {
console.log(reason)
})
p.then((value) => {
console.log(value)
}, (reason) => {
console.log(reason)
})
我们知道fs.readFile
是一个异步操作,也就是意味着我们同步调用then
方法的时候,当前promise
的状态还有可能是pending
状态,接下来我们实现支持异步调用
const SUCCESS = 'fulfilled'
const FALL = 'rejected'
const PENDING = 'pending'
class Promisse {
constructor(executor) {
this.status = PENDING
this.value = undefined
this.reason = undefined
this.onResolveCallBacks = [] // 新
this.onRejtectCallBacks = [] // 新
const resolve = (value) => {
if (this.status === PENDING) {
this.status = SUCCESS
this.value = value
this.onResolveCallBacks.forEach(fn => fn())// 将缓存起来的成功的回调遍历执行
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.status = FALL
this.reason = reason
this.onRejtectCallBacks.forEach(fn => fn()) // 将缓存起来的失败的回调遍历执行
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFullFilled, onRejected) {
if (this.status === SUCCESS) {
onFullFilled(this.value)
}
if (this.status === FALL) {
onRejected(this.reason)
}
if (this.status === PENDING) { // 如果当前状态是等待状态,也就是可能是异步调用,我们将对应的回调存起来
this.onResolveCallBacks.push(() => {
onFullFilled(this.value)
})
this.onRejtectCallBacks.push(() => {
onRejected(this.reason)
})
}
}
}
module.exports = Promisse
首先我们用2个变量onResolveCallBacks
onRejtectCallBacks
来缓存成功和失败的回调
当我们执行then
操作的时候,如果当前状态还是pending
状态,那我们就需要将回调缓存起来,当异步调用了resolve
或者reject
的时候再将缓存遍历执行
# 实现then的链式调用
我们开发时候最常用的写法就是链式的调用promise
了,如
let Promise = require('./promise')
const fs = require('fs')
function read(filePath){
return new Promise((resolve, reject)=> {
fs.readFile(filePath, 'utf8', function (err, data) {
if (err) {
return reject(err)
}
resolve(data)
})
})
}
read('./name.txt').then(function(data) {
return 1000
}).then(data => {
console.log(data)
})
但是我们promise
的链式调用和Jquery的链式调用不太一样,jq是通过返回this来达到对应的效果,而我们promise
的状态一旦变为成功或者失败状态就无法进行改变,所以promise
链式调用的核心在于每一个then
执行之后,返回一个新的promise
TIP
我们需要知道一些then的规则
如果then中返回一个不是promise的值,那么这个值会作为下一个then的参数
如果then中返回一个promise,那么这个promise成功或者失败的值会作为下一个then中的参数
我们主要修改一下then
方法,让他按照对应的规则返回一个新的promise
then(onFullFilled, onRejected) {
let newPromise
newPromise = new Promise((resolve, reject) => {
if (this.status === SUCCESS) {
setTimeout(() => {
try {
let x = onFullFilled(this.value)
resolvePromise(newPromise, x, resolve, reject)
} catch (err) {
reject(err)
}
})
}
if (this.status === FALL) {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(newPromise, x, resolve, reject)
} catch (err) {
reject(err)
}
})
}
if (this.status === PENDING) {
this.onResolveCallBacks.push(() => {
setTimeout(() => {
try {
let x = onFullFilled(this.value)
resolvePromise(newPromise, x, resolve, reject)
} catch (err) {
reject(err)
}
})
})
this.onRejtectCallBacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(newPromise, x, resolve, reject)
} catch (err) {
reject(err)
}
})
})
}
})
return newPromise
}
按照Promise A+
规范,我们需要写一个resolvePromise
方法来处理promise的返回值
function resolvePromise(newPromise, x, resolve, reject) {
if (newPromise === x) { // 根据A+规范 如果then中返回当前then的所属promise 应该报类型错误,避免死循环
return reject(new TypeError(`TypeErr`))
}
if (typeof x === 'function' || (typeof x === 'object' && x !== null)) {
try {
let then = x.then
if (typeof then === 'function') { //如果x是一个promise
then.call(x, y => { //如果promise的结果是成功的,把他往下传 ,如果是失败的就让下一个promise也失败
resolvePromise(newPromise, y, resolve, reject) //避免调用resolve的时候传入一个promise
}, err => {
reject(err)
})
} else { // x是一个普通对象
resolve(x)
}
} catch (e) {
reject(e)
}
} else { // x是一个常量
resolve(x)
}
}
处理的规则大致为:
报错直接调用返回的新的promise
的reject
方法
如果上一个promise
的值是不为promise
的值,直接调用新的promise
的resolve
方法
然后判断上一个promise
的值是不是和新的promise
是否相等,想等的话会抛出一个类型错误
然后判断上一个promise
的返回值是不是一个promise
, 如果是就调用这个promise
的方法
# 实现值的穿透
我们经常会看到这些代码
Premise.resolve(100).then().then().then(() => {
console.log(data) // 100
})
这个其实并不复杂,我们来实现以下
then(onFullFilled, onRejected) {
onFullFilled = typeof onFullFilled === 'function' ? onFullFilled : val => val // 实现值的穿透,如果传入的是函数走原来的逻辑,如果不是函数直接返回当前值
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err } // 如果传递的不是函数,则抛出错误让函数接下来捕获
let newPromise
...
}
我们只需要在then
的时候对传入的参数进行类型判断就好了,以对onFullFilled
的处理为例子,如果不是一个函数,则将val直接返回给下一个then
TIP
其实我们看到的then().catch(e)中的catch,其实就是.then(null, fn)的语法糖,也是利用了值的传递的原理
直接上代码吧
catch(cb) {
return this.then(null, cb)
}
# 实现Promise.resolve
Promise.resolve
可以把一个值包装成一个promise
,也很简单
Promise.resolve = function(value) {
return new Promise((resolve, rejetc) => {
resolve(value)
})
}
# 实现finally
finally
的意思是无论如何promise
都会走到finally
里面去
但是有一点需要注意的是如果finally
里面返回一个promise
,代码会等待这个promise
执行完之后才会往下执行
new Promise((resolve, reject) => {
resolve(1000)
}).finally(() => {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, 3000)
})
}).then(data => {
console.log(data)
})
上述代码会在finally
理等待3秒,然后再执行后面的then
那我们来简单实现一个finally
吧
Promise.prototype.finally = function(cb) {
return this.then((data) => {
return Promise.resolve(cb()).then(() => data)
}, (err) => {
return Promise.resolve(cb()).then(() => { throw err })
})
}
主要借助了如果promise
里面返回一个promise
的话会等着里面的promise
执行这个特性,然后也是then
的一个语法糖
# 实现Promise.all
先简单看下Promise.all
的基础用法吧
let fs = require('fs').promises
Promise.all([fs.readFile('./name.txt', 'utf8'), fs.readFile('./age.txt', 'utf8'), 3])
.then((data) => {
console.log(data)
})
不管是同步代码还是异步代码,promise.all
都会等待这个代码执行拿到结果,并且按照数组的顺序返回对应的结果
那我们来简单的实现一下吧
function isPromise(value) {
if (typeof value === 'function' || (typeof value === 'object' && typeof value !== null)) {
if (typeof value.then === 'function') {
return true
}
}
return false
}
Promise.all = function (values) {
return new Promise((resole, reject) => {
let arr = []
let i = 0;
let processData = (key, value) => {
arr[key] = value
if (++i === values.length) {
resole(arr)
}
}
for (let i = 0; i < values.length; i++) {
let current = values[i]
if (isPromise(current)) {
current.then(y => {
processData(i ,y)
}, reject)
} else {
processData(i, current)
}
}
})
}
# 实现Promise.race
Promise.race
的用法和all
有点像,不过他是如果有一个成功了,就返回这个成功的值,也就是返回最快的值,如果有一个失败了,就返回失败的值
我们直接来实现一下他吧
Promise.race = function (values) {
return new Promise((resolve, reject) => {
for (let i = 0; i < values.length; i++) {
let current = values[i]
if (isPromise(current)) {
current.then(resole, reject)
} else {
resolve(current)
}
}
})
}
# 终止promise
在项目中我们经常遇到这样的需求,如果接口超过3秒没有返回,那我们就可以终止这个接口返回,其实是利用了race
的特点,我们来模拟一下吧
let p = new Promise((resolve, reject) => { // 模拟接口请求 需要3秒
setTimeout(() => {
resolve(123)
}, 3000)
})
// 现在我们需要在2秒的时候就不等待这个请求的结果了
function wrap(promise) {
let abort
let newPromise = new Promise((resolve, reject) => {
abort = reject
})
let p = Promise.race([newPromise, promise])
p.abort = abort
return p
}
let p1 = wrap(p);
setTimeout(() => { // 2秒的时候就不等待接口返回的结果了
p1.abort()
}, 2000)
p1.then(data => {
console.log(data)
}).catch((e) => {
console.log(e)
})
大致意思就是这个请求的结果我不要了,但是请求还是会发出去的