1. 前言
面试的时候经常会遇到面试官问Promise
是如何实现的?面试官想了解的是你对Promise
对象的掌握,如果你能够从Promise
是什么,有什么特点,内部实现包括then
方法的链式调用实现来回答,肯定会让面试官满意的。
本文将尝试由浅入深,手写一个符合规范的Promise
对象,希望对大家面试和理解Promise
有所帮助。
2. Promise 是什么
所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise
是一个对象,从它可以获取异步操作的消息。Promise
提供统一的API
,各种异步操作都可以用同样的方法进行处理。
Promise
出现之前都是通过回调函数来实现,回调函数本身没有问题,但是嵌套层级过深,很容易掉进回调地狱
。
const fs = require('fs');
fs.readFile('1.txt', (err,data) => {
fs.readFile('2.txt', (err,data) => {
fs.readFile('3.txt', (err,data) => {
//可能还有后续代码
});
});
});
如果每次读取文件后还要进行逻辑的判断或者异常的处理,那么整个回调函数就会非常复杂且难以维护。
Promise
的出现正是为了解决这个痛点,我们可以把上面的回调嵌套用Promise
改写一下:
// 使用Promise读取文件
const readFile = function(fileName){
return new Promise((resolve, reject)=>{
fs.readFile(fileName, (err, data)=>{
if(err){
reject(err)
} else {
resolve(data)
}
})
})
}
// 调用
readFile('1.txt')
.then(data => {
return readFile('2.txt');
}).then(data => {
return readFile('3.txt');
}).then(data => {
//...
});
3. Promise 基本结构
我们先回顾一下,我们平时都是怎么使用Promise
的:
var p = new Promise(function(resolve, reject){
console.log('执行')
setTimeout(function(){
resolve(2)
}, 1000)
})
p.then(function(res){
console.log('suc',res)
},function(err){
console.log('err',err)
})
首先看出来,Promise
是通过构造函数实例化一个对象,然后通过实例对象上的then
方法,来处理异步返回的结果。
4. Promise 对象的特点
4.1. 对象的状态不受外界影响
Promise
对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(executor) {
var This = this
this.state = PENDING; //状态
this.value = undefined; //成功结果
this.reason = undefined; //失败原因
function resolve(value) {}
function reject(reason) {}
}
Promise.prototype.then = function (onFulfilled, onRejected) {};
module.exports = Promise;
当我们实例化Promise
时,构造函数会马上调用传入的执行函数executor
,我们可以试一下:
let p = new Promise((resolve, reject) => {
console.log('执行了');
});
因此在Promise
中构造函数立马执行,同时将resolve
函数和reject
函数作为参数传入:
function Promise(executor) {
var This = this
this.state = PENDING; //状态
this.value = undefined; //成功结果
this.reason = undefined; //失败原因
function resolve(value) {}
function reject(reason) {}
executor(resolve, reject)
}
但是executor
也会可能存在异常,因此通过try/catch
来捕获一下异常情况:
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
4.2. 状态不可再变
一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved
(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。
所以Promise
在回调函数resolve
和reject
中判断,只能是pending
状态的时候才能更改状态:
function resolve(value) {
if(This.state === PENDING){
This.state = FULFILLED
This.value = value
}
}
function reject(reason) {
if(This.state === PENDING){
This.state = REJECTED
This.reason = reason
}
}
在更改状态的同时,将回调函数中成功的结果或者失败的原因都保存在对应的属性中,方便以后来获取。
5. 实现支持异步的then方法
当Promise
的状态改变之后,不管成功还是失败,都会触发then
回调函数。因此,then
的实现也很简单,就是根据状态的不同,来调用不同处理终值的函数。
Promise.prototype.then = function (onFulfilled, onRejected) {
if(this.state === FULFILLED){
typeof onFulfilled === 'function' && onFulfilled(this.value)
}
if(this.state === REJECTED){
typeof onRejected === 'function' && onRejected(this.reason)
}
};
onFulfilled和onRejected是可选的,因此我们对两个值进行一下类型的判断:
onFulfilled 和 onRejected 都是可选参数。如果 onFulfilled 不是函数,其必须被忽略。如果 onRejected 不是函数,其必须被忽略。
到这一步我们基本已经完成了Promise
的代码,但是它还不能工作,因为当then
里面函数运行时,resolve
由于是异步执行的,还没有来得及修改state
,此时还是PENDING
状态;因此我们需要对异步的情况做一下处理。
那么如何让我们的Promise
来支持异步呢?我们可以参考发布订阅模式,在执行then
方法的时候,如果当前还是PENDING
状态,就把回调函数寄存到一个数组中,当状态发生改变时,去数组中取出回调函数;因此我们先在Promise
中定义一下变量:
function Promise(executor) {
this.onFulfilled = [];//成功的回调
this.onRejected = []; //失败的回调
}
这样,当then
执行时,如果还是PENDING
状态,我们不是马上去执行回调函数,而是将其存储起来:
Promise.prototype.then = function (onFulfilled, onRejected) {
if(this.state === FULFILLED){
typeof onFulfilled === 'function' && onFulfilled(this.value)
}
if(this.state === REJECTED){
typeof onRejected === 'function' && onRejected(this.reason)
}
if(this.state === PENDING){
typeof onFulfilled === 'function' && this.onFulfilled.push(onFulfilled)
typeof onRejected === 'function' && this.onRejected.push(onRejected)
}
};
存储起来后,当resolve
或者reject
异步执行的时候就可以来调用了:
function resolve(value) {
if(This.state === PENDING){
This.state = FULFILLED
This.value = value
This.onFulfilled.forEach(fn => fn(value))
}
}
function reject(reason) {
if(This.state === PENDING){
This.state = REJECTED
This.reason = reason
This.onRejected.forEach(fn => fn(reason))
}
}
有同学可能会提出疑问了,为什么这边onFulfilled
和onRejected
要存在数组中,直接用一个变量接收不是也可以么?下面看一个例子:
var p = new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve(4)
}, 0)
})
p.then((res)=>{
//4 res
console.log(res, 'res')
})
p.then((res1)=>{
//4 res1
console.log(res1, 'res1')
})
我们分别调用了两次then
,如果是一个变量的话,最后肯定只会运行后一个then
,把之前的覆盖了,如果是数组的话,两个then
都能正常运行。
至此,我们运行demo
,就能如愿以偿的看到运行结果了;一个四十行左右的简单Promise就此完成了。
这里贴一下完整的代码:
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function Promise(executor) {
var This = this
this.state = PENDING; //状态
this.value = undefined; //成功结果
this.reason = undefined; //失败原因
this.onFulfilled = [];//成功的回调
this.onRejected = []; //失败的回调
function resolve(value) {
if(This.state === PENDING){
This.state = FULFILLED
This.value = value
This.onFulfilled.forEach(fn => fn(value))
}
}
function reject(reason) {
if(This.state === PENDING){
This.state = REJECTED
This.reason = reason
This.onRejected.forEach(fn => fn(reason))
}
}
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
if(this.state === FULFILLED){
typeof onFulfilled === 'function' && onFulfilled(this.value)
}
if(this.state === REJECTED){
typeof onRejected === 'function' && onRejected(this.reason)
}
if(this.state === PENDING){
typeof onFulfilled === 'function' && this.onFulfilled.push(onFulfilled)
typeof onRejected === 'function' && this.onRejected.push(onRejected)
}
};
// 导出模块,因为我的demo是在node控制台运行的,所以这导出成node支持的模块
module.exports = Promise;
Demo代码:
const Promise = require('./Promise')
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello')
}, 0)
})
p1.then(res => {
console.log('res0 => ', res)
})
p1.then(res => {
console.log('res1 => ', res)
})
// 运行结果
// res0 => hello
// res1 => hello
6. 让then方法支持链式调用
then
方法必须返回一个新的Promise
对象,这样才能实现then
方法的链式调用,上面的代码什么都没有返回,所以是不支持链式调用的,让我们对then
方法做一些改写。
Promise.prototype.then = function (onFulfilled, onRejected) {
// 不论then进行什么操作,都返回一个新的Promise对象
var This = this // This是myPromise的实例对象
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
// 定义resolvePromise方法
function resolvePromise(myPromise, fulfilled, resolve, reject) {
// 避免调用死循环
if(myPromise === fulfilled){
reject(new TypeError('Chaining cycle'))
}
// 判断fulfilled是对象还是方法
if(fulfilled && typeof fulfilled === 'object' || typeof fulfilled === 'function'){
let used;
// 防止使用Object.defineProperty()恶意抛错,这里使用try/catch预防
try {
let then = fulfilled.then
if(typeof then === 'function'){
then.call(fulfilled, (res)=>{
if (used) return;
used = true
resolvePromise(myPromise, res, resolve, reject)
}, (r) =>{
if (used) return;
used = true
reject(r)
})
} else {
if (used) return;
used = true
resolve(fulfilled)
}
} catch(e){
if (used) return;
used = true
reject(e)
}
} else {
resolve(fulfilled)
}
}
// 让then方法返回一个新的Promise对象
const myPromise = new Promise((resolve, reject) => {
if(This.state === FULFILLED){
setTimeout(()=>{
try {
let fulfilled = onFulfilled(This.value)
resolvePromise(myPromise, fulfilled, resolve, reject)
} catch (error) {
reject(error)
}
})
} else if(This.state === REJECTED){
setTimeout(()=>{
try {
let fulfilled = onRejected(This.reason)
resolvePromise(myPromise, fulfilled, resolve, reject)
} catch (error) {
reject(error)
}
})
} else if(This.state === PENDING){
This.onFulfilled.push(()=>{
setTimeout(()=>{
try {
let fulfilled = onFulfilled(This.value)
resolvePromise(myPromise, fulfilled, resolve, reject)
} catch (error) {
reject(error)
}
})
})
This.onRejected.push(()=>{
setTimeout(()=>{
try {
let fulfilled = onRejected(This.reason)
resolvePromise(myPromise, fulfilled, resolve, reject)
} catch (error) {
reject(error)
}
})
})
}
})
return myPromise
}
7. 最终版完整代码
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
// 定义对象的构造函数
function Promise(executor) {
var This = this
this.state = PENDING; //状态
this.value = undefined; //成功结果
this.reason = undefined; //失败原因
this.onFulfilled = [];//成功的回调
this.onRejected = []; //失败的回调
function resolve(value) {
if(This.state === PENDING){
This.state = FULFILLED
This.value = value
This.onFulfilled.forEach(fn => fn(value))
}
}
function reject(reason) {
if(This.state === PENDING){
This.state = REJECTED
This.reason = reason
This.onRejected.forEach(fn => fn(reason))
}
}
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
// 实现then方法
Promise.prototype.then = function (onFulfilled, onRejected) {
// 不论then进行什么操作,都返回一个新的Promise对象
var This = this // This是myPromise的实例对象
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
// 定义resolvePromise方法
function resolvePromise(myPromise, fulfilled, resolve, reject) {
// 避免调用死循环
if(myPromise === fulfilled){
reject(new TypeError('Chaining cycle'))
}
// 判断fulfilled是对象还是方法
if(fulfilled && typeof fulfilled === 'object' || typeof fulfilled === 'function'){
let used;
// 防止使用Object.defineProperty()恶意抛错,这里使用try/catch预防
try {
let then = fulfilled.then
if(typeof then === 'function'){
then.call(fulfilled, (res)=>{
if (used) return;
used = true
resolvePromise(myPromise, res, resolve, reject)
}, (r) =>{
if (used) return;
used = true
reject(r)
})
} else {
if (used) return;
used = true
resolve(fulfilled)
}
} catch(e){
if (used) return;
used = true
reject(e)
}
} else {
resolve(fulfilled)
}
}
// 让then方法返回一个新的Promise对象
const myPromise = new Promise((resolve, reject) => {
if(This.state === FULFILLED){
setTimeout(()=>{
try {
let fulfilled = onFulfilled(This.value)
resolvePromise(myPromise, fulfilled, resolve, reject)
} catch (error) {
reject(error)
}
})
} else if(This.state === REJECTED){
setTimeout(()=>{
try {
let fulfilled = onRejected(This.reason)
resolvePromise(myPromise, fulfilled, resolve, reject)
} catch (error) {
reject(error)
}
})
} else if(This.state === PENDING){
This.onFulfilled.push(()=>{
setTimeout(()=>{
try {
let fulfilled = onFulfilled(This.value)
resolvePromise(myPromise, fulfilled, resolve, reject)
} catch (error) {
reject(error)
}
})
})
This.onRejected.push(()=>{
setTimeout(()=>{
try {
let fulfilled = onRejected(This.reason)
resolvePromise(myPromise, fulfilled, resolve, reject)
} catch (error) {
reject(error)
}
})
})
}
})
return myPromise
}