共计 6015 个字符,预计需要花费 16 分钟才能阅读完成。
前言
Promise 在开发中咱们常常用到,它解决了回调天堂问题,对谬误的解决也十分不便。本文我将通过一步步欠缺 Promise,从简略到简单的过程来说。
本文适宜纯熟使用 Promise 的人浏览。
极简版
首先 Promise
是一个类,类中的构造函数须要接管一个执行函数 executor
默认就会执行,它有两个参数:resolve
和 reject
,这两个参数是Promise
外部定义的两个函数,用来扭转状态并执行对应回调函数。
默认创立一个 Promise
状态就是 pending
,promise
只有三种状态:pending
,fulfilled
,rejected
,调用胜利 resolve
和失败 reject
办法时,须要传递一个胜利的起因 / 值 value
和失败的起因 reason
。每一个promise
实例都有一个 then 办法。
Promise
状态一经扭转就不能再扭转,故咱们限度只能在状态为 pending
才扭转,这样就保障状态只能扭转一次。
如果抛出异样依照失败来解决。
依照以上 Promise
的根本要求,就有个根本构造:
const STATUS = {
PENDING: 'PENDING',
FULFILLED: 'FULFILLED',
REJECTED: 'REJECTED',
}
class Promise {constructor(executor) {
this.status = STATUS.PENDING
this.value = undefined // 胜利的值
this.reason = undefined // 失败起因
this.onResolvedCallbacks = [] // 寄存胜利的回调
this.onRejectedCallbacks = [] // 寄存失败的回调
const resolve = val => {if (this.status === STATUS.PENDING) {
this.status = STATUS.FULFILLED
this.value = val
this.onResolvedCallbacks.forEach(fn => fn())
}
}
const reject = reason => {if (this.status === STATUS.PENDING) {
this.status = STATUS.REJECTED
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {executor(resolve, reject)
} catch (e) {console.log(e)
}
}
then(onFulfilled, onRejected) {if (this.status === STATUS.FULFILLED) {onFulfilled(this.value)
}
if (this.status === STATUS.REJECTED) {onRejected(this.reason)
}
if (this.status === STATUS.PENDING) {this.onResolvedCallbacks.push(() => {onFulfilled(this.value)
})
this.onRejectedCallbacks.push(() => {onRejected(this.reason)
})
}
}
}
下面代码中,为什么不间接执行回调还要存储呢?因为如果当 resolve(3)
被提早执行时,此时常理写代码来说 then 是会被执行的,但此时没有 resolve
,故p
的状态应为 pending
,不应立即执行胜利调用的函数,须要把它存起来,直到执行resolve
再执行胜利调用的函数。
let p = new Promise((resolve, reject) => {setTimeout(() => {resolve(3)
}, 1000)
})
p.then(res => {console.log('then1', res)
return 2
})
链式调用
链式调用想必大家多少有点理解,在 jQuery 外面的链式调用则是返回 this,而 Promise 外面的链接调用则是返回一个新的 Promise 对象。
let p = new Promise((resolve, reject) => {setTimeout(() => {resolve(3)
}, 1000)
})
p.then(res => {console.log('then1', res) // then1 3
return 2
}).then(res => {
// 链式调用
console.log('then2', res) // then2 2
})
批改 then
办法和减少 catch
办法
then(onFulfilled, onRejected) {let nextPromise = new Promise((resolve, reject) => {if (this.status === STATUS.FULFILLED) {setTimeout(() => {
try {let res = onFulfilled(this.value)
resolve(res)
} catch (e) {reject(e)
}
})
}
if (this.status === STATUS.REJECTED) {setTimeout(() => {
try {let res = onRejected(this.reason)
resolve(res)
} catch (e) {reject(e)
}
})
}
if (this.status === STATUS.PENDING) {this.onResolvedCallbacks.push(() => {setTimeout(() => {
try {let res = onFulfilled(this.value)
resolve(res)
} catch (e) {reject(e)
}
})
})
this.onRejectedCallbacks.push(() => {setTimeout(() => {
try {let res = onRejected(this.reason)
resolve(res)
} catch (e) {reject(e)
}
})
})
}
})
return nextPromise
}
catch(err) {
// 默认没有胜利,只有失败
return this.then(undefined, err)
}
此次批改咱们是在最外层包了新的Promise
,而后加了个 setTimeout 模仿微工作(因为这里用的 setTimeout 模仿微工作,所以 JS 事件循环执行程序上和原生 Promise 有区别),把回调放入,期待确保异步执行。
链式调用进阶版
下面的 Promise
仍旧没有过关,因为如果链式调用中 Promise
返回的是一般值,就应该把值包装成新的 Promise
对象
- 每个 then 办法都返回一个新的 Promise 对象(重点)
- 如果 then 办法返回了一个 Promise 对象,则须要查看它的状态,如果状态是胜利,则调用
resolve
办法,把胜利的状态传递给它;如果是失败的,则把失败的状态传递给下一个Promise
对象。 - 如果 then 办法中返回的是一个原始数据类型值(如 Number、String 等)就应用此值包装成一个新的 Promise 对象返回。
- 如果 then 办法中没有 return 语句,则返回一个用 undefined 包装的 Promise 对象
- 如果 then 办法没有传入任何回调,则持续向下传递(值的传递个性)。
- 如果是循环援用则须要抛出谬误
批改 then
办法
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
onRejected =
typeof onRejected === 'function'
? onRejected
: err => {throw err}
let nextPromise = new Promise((resolve, reject) => {if (this.status === STATUS.FULFILLED) {setTimeout(() => {
try {let res = onFulfilled(this.value)
resolvePromise(res, nextPromise, resolve, reject)
} catch (e) {reject(e)
}
})
}
if (this.status === STATUS.REJECTED) {setTimeout(() => {
try {let res = onRejected(this.reason)
resolvePromise(res, nextPromise, resolve, reject)
} catch (e) {reject(e)
}
})
}
if (this.status === STATUS.PENDING) {this.onResolvedCallbacks.push(() => {setTimeout(() => {
try {let res = onFulfilled(this.value)
resolvePromise(res, nextPromise, resolve, reject)
} catch (e) {reject(e)
}
})
})
this.onRejectedCallbacks.push(() => {setTimeout(() => {
try {let res = onRejected(this.reason)
resolve(res)
} catch (e) {reject(e)
}
})
})
}
})
return nextPromise
}
外面减少了一个 resolvePromise
函数,解决调用 then
办法后不同返回值的状况,实现如下:
function resolvePromise(x, nextPromise, resolve, reject) {if (x === nextPromise) {
// x 和 nextPromise 指向同一对象,循环援用抛出谬误
return reject(new TypeError('循环援用'))
} else if (x && (typeof x === 'object' || typeof x === 'function')) {
// x 是对象或者函数
let called = false // 防止屡次调用
try {
let then = x.then // 判断对象是否有 then 办法
if (typeof then === 'function') {
// then 是函数,就判定 x 是一个 Promise(依据 Promise A+ 标准)then.call(
x,
function (y) {
// 调用返回的 promise,用它的后果作为下一次 then 的后果
if (called) return
called = true
resolvePromise(y, nextPromise, resolve, reject) // 递归解析胜利后的值,直到它是一个一般值为止
},
function (r) {if (called) return
called = true
reject(r) // 取 then 时产生谬误了
}
)
} else {resolve(x) // 此时 x 就是一个一般对象
}
} catch (e) {reject(e)
}
} else {
// x 是原始数据类型 / 没有返回值,这里即是 undefined
resolve(x)
}
}
Promise.resolve() 实现
class Promise {
// ...
static resolve(val) {return new Promise((resolve, reject) => {resolve(val)
})
}
}
Promise.reject() 实现
class Promise {
// ...
static reject(reason) {return new Promise((resolve, reject) => {reject(reason)
})
}
}
Promise.prototype.finally 实现
该办法次要有两大重点
- 无论以后这个 Promise 对象最终的状态是胜利还是失败 ,finally 办法里的回调函数都会执行一次
- 在 finally 办法前面能够持续链式调用 then 办法,拿到以后这个 Promise 对象最终返回的后果
Promise.prototype.finally = function (callback) {
return this.then(
data => {
// 让函数执行,外部会调用办法,如果办法是 promise 须要期待它实现
return Promise.resolve(callback()).then(() => data)
},
err => {return Promise.resolve(callback()).then(() => {throw err})
}
)
}
Promise.all() 实现
Promise.all 能够将多个 Promise 实例包装成一个新的 Promise 实例。同时,胜利和失败的返回值是不同的,胜利的时候返回的是一个后果数组,而失败的时候则返回最先被 reject 失败状态的值。
Promise.all = function (promises) {if (!Array.isArray(promises)) {throw new Error('Not a array')
}
return new Promise((resolve, reject) => {let result = []
let times = 0 // 计数器
function processData(index, val) {result[index] = val
if (++times === promises.length) {resolve(result)
}
}
for (let i = 0; i < promises.length; i++) {let p = promises[i]
if (isPromise(p)) {
// Promise 对象
p.then(data => {processData(i, data)
}, reject)
} else {processData(i, p) // 一般值
}
}
})
}
Promise.race() 实现
在执行多个异步操作中,只保留取第一个执行实现的异步操作的后果,即是哪个后果取得的快,就返回那个后果,不论后果自身是胜利状态还是失败状态。其余的办法仍在执行,不过执行后果会被摈弃。
Promise.race = function (promises) {if (!Array.isArray(promises)) {throw new Error('Not a array')
}
return new Promise((resolve, reject) => {if (promises.length === 0) {return} else {for (let p of promises) {resolve(p).then(
value => {resolve(value)
},
reason => {reject(reason)
}
)
}
}
})
}
到此为止,Promise 就实现实现了。
残缺源码地址
- ps:集体技术博文 Github 仓库,感觉不错的话欢送 star,激励我持续写作吧~