我们面试的时候经常会问到Promise的使用;有的面试官再深入一点,会继续问是否了解Promise的实现方式,或者有没有阅读过Promise的源码;今天我们就来看一下,Promise在内部是如何实现来链式调用的。
首先,我们先回顾一下promise的基本使用:
let res = new Promise((resolve) => {
setTimeout(() => {
resolve("成功了");
}, 1000);
});
res.then((data) => {
// 过一秒后打印出 => 成功了
console.log("data:", data);
});
-
从这儿我们能够看出来
-
1、Promise是通过构造函数实例化一个对象,然后通过实例对象上的then方法,来处理异步返回的结果,并且他接受一个函数(该函数接受两个方法作为参数=>resolve,reject)作为参数,
-
2、promise内部会保存失败和成功的结果,即resolve和reject的结果
-
3、promise对象内部有一个resolve方法和一个resolve方法
-
4、promise的对象上面有一个then方法
-
Promise A+ 规范也说
-
1、每一个promise对象初始化的时候,他的状态一定是pending
-
2、一个promise对象对应着三种状态 pending, fulfilled, rejected
-
3、promise对象的状态如果是成功或者失败的状态,就不能在变为其它的状态,如果状态为pending,可以继续改变状态为成功或者失败
所以我们定义一个基本的promise
//先定义三种状态
const fulfilled = "fulfilled"; //成功
const rejected = "rejected"; // 失败
const pending = "pending"; // 进行中
class Promise {
constructor(executor) {
//每一个promise对象,最开始的状态肯定是pending
this.status = pending;
this.value = ""; //存储成功的原因
this.reason = ""; //存储失败的原因
// 调用resolve 方法会把当前promise对象的状态变为fulfilled,并且会执行then方法的回调
let resolve = (value) => {
if (this.status === pending) {
this.status = fulfilled; //改变状态
this.value = value; // 保存成功的值
}
};
// 调用reject 方法会把当前promise对象的状态变为rejected,并且会执行catch方法的回调
let reject = (reason) => {
if (this.status === pending) {
this.status = rejected; // 改变状态
this.reason = reason; // 保存失败的值
}
};
}
// then方法会接受两个参数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
then(onFulfilled, onRejected) {}
}
其次, 我们传入promise构造函数的execute是一个立即执行函数,我们可以试一下:
new Promise((res) => {
console.log("立即执行"); // 会立即打印出结果
});
所以我们需要在构造函数中执行一下这个立即执行函数:
// 立即执行,为了防止执行报错,我们tryCatch一下,如果报错了,就把promise的状态改变为失败
try {
executor(resolve, reject);
} catch (e) {
if (this.status === pending) {
this.status = rejected;
this.reason = e;
}
}
then方法的实现:
当Promise的状态改变之后,不管成功还是失败,都会触发then回调函数。因此,then的实现也很简单,就是根据状态的不同,来调用不同处理终值的函数。
再来回顾一下,我们调用了execute中的resolve或者reject的时候,对应的then返回中的参数函数才会执行,说明
在执行了resolve和reject之后,响应的then方法中的函数才会去执行
但是当then里面函数运行时,resolve由于可能是异步执行的,还没有来得及修改state,此时还是PENDING状态;因此我们需要对异步的情况做一下处理。
使用两个数组去分别保存失败和成功的回调函数
constructor(executor) {
this.successFn = []; // 保存成功的回调
this.failFn = []; //保存失败的回调
}
所以then方法实现如下:
// then方法会接受两个参数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
then(onFulfilled, onRejected) {
//参数的可选
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (val) => val;
onRejected =
typeof onRejected === "function"
? onRejected
: (err) => {
throw err;
};
// 由于我们调用then方法的时候,promise对象的状态可能还是pending状态(异步的情况),这个时候我们就先要保存他的成功或者失败的回调,然后等状态改变了再去调用
if (this.status === pending) {
this.successFn.push(onFulfilled);
this.failFn.push(onRejected);
}
//如果已经成功了
if (this.status === fulfilled) {
onFulfilled(this.value);
}
//如果已经失败了
if (this.status === rejected) {
onRejected(this.reason);
}
}
对应的构造函数中增加两个数组:
constructor(executor) {
//每一个promise对象,最开始的状态肯定是pending
this.status = pending;
this.value = ""; //存储成功的原因
this.reason = ""; //存储失败的原因
this.successFn = []; // 保存成功的回调
this.failFn = []; //保存失败的回调
}
然后resolve和reject函数也做相应的修改
let resolve = (value) => {
if (this.status === pending) {
this.status = fulfilled; //改变状态
this.value = value; // 保存成功的值
//改变状态了 ,去执行then方法中成功的回调函数
if (this.successFn.length > 0) {
this.successFn.forEach((fn) => fn(this.value));
}
}
};
// 调用reject 方法会把当前promise对象的状态变为rejected,并且会执行catch方法的回调
let reject = (reason) => {
if (this.status === pending) {
this.status = rejected; // 改变状态
this.reason = reason; // 保存失败的值
// 执行失败的回调
if (this.failFn.length > 0) {
this.failFn.forEach((fn) => fn(this.reason));
}
}
};
到这里一个简易版的promise就实现了,这个promise支持简单的使用,支持异步,但是现在还不支持链式调用
贴一下代码:
//先定义三种状态
const fulfilled = "fulfilled"; //成功
const rejected = "rejected"; // 失败
const pending = "pending"; // 进行中
class Promise {
constructor(executor) {
//每一个promise对象,最开始的状态肯定是pending
this.status = pending;
this.value = ""; //存储成功的原因
this.reason = ""; //存储失败的原因
this.successFn = []; // 保存成功的回调
this.failFn = []; //保存失败的回调
// 调用resolve 方法会把当前promise对象的状态变为fulfilled,并且会执行then方法的回调
let resolve = (value) => {
if (this.status === pending) {
this.status = fulfilled; //改变状态
this.value = value; // 保存成功的值
//改变状态了 ,去执行then方法中成功的回调函数
if (this.successFn.length > 0) {
this.successFn.forEach((fn) => fn(this.value));
}
}
};
// 调用reject 方法会把当前promise对象的状态变为rejected,并且会执行catch方法的回调
let reject = (reason) => {
if (this.status === pending) {
this.status = rejected; // 改变状态
this.reason = reason; // 保存失败的值
if (this.failFn.length > 0) {
this.failFn.forEach((fn) => fn(this.reason));
}
}
};
// 立即执行,为了防止报错,我们tryCatch一下,如果报错了,就把promise的状态改变为失败
try {
executor(resolve, reject);
} catch (e) {
if (this.status === pending) {
this.status = rejected;
this.reason = e;
}
}
}
// then方法会接受两个参数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
then(onFulfilled, onRejected) {
//参数的可选
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (val) => val;
onRejected =
typeof onRejected === "function"
? onRejected
: (err) => {
throw err;
};
// 由于我们调用then方法的时候,promise对象的状态可能还是pending状态,这个时候我们就先要保存他的成功或者失败的回调,然后等状态改变了再去调用
if (this.status === pending) {
this.successFn.push(onFulfilled);
this.failFn.push(onRejected);
}
//如果已经成功了
if (this.status === fulfilled) {
onFulfilled(this.value);
}
//如果已经失败了
if (this.status === rejected) {
onRejected(this.reason);
}
}
}
export default Promise;
接下来,我们看看如何实现promise 的链式调用
其实在promise中,链式调用才是promise的核心,我们对照promise/A+规范,一步一步地来实现,我们先来看一下规范是如何来定义的:
上图中主要说明这么几个点:
- 1、then方法必须返回一个新的promise对象
- 2、如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
- 3、如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
- 4、如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
- 5、如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因
也就是说,每个then方法都要返回一个新的Promise对象,这样我们的then方法才能不断的链式调用;我们对上面的代码进行简单的改写,不论then进行什么操作,都返回一个新的Promise对象:
then(){
let promise2 = new Promise((resolve, reject) => {
})
return promise2
}
从这个代码中我们可以看到,then方法中会返回一个新的promise对象,我们暂且称他为promise2
上图所选中的地方说明x和promise2不能是同一个对象,不然需要抛出一个类型错误
下面,我们来改写一下then方法:
// then方法会接受两个参数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
then(onFulfilled, onRejected) {
//参数的可选
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (val) => val;
onRejected =
typeof onRejected === "function"
? onRejected
: (err) => {
throw err;
};
let promise2 = new Promise((resolve, reject) => {
// 由于我们调用then方法的时候,promise对象的状态可能还是pending状态,这个时候我们就先要保存他的成功或者失败的回调,然后等状态改变了再去调用
if (this.status === pending) {
onFulfilled && this.successFn.push(() => {
try {
let x = onFulfilled(this.value);
// x这个返回值也有可能是一个promise对象,所以需要resolvePromise这个处理程序去进行比对一下
// 判断x和promise2是不是同一个promise
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
onRejected && this.failFn.push(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
//如果已经成功了
if (this.status === fulfilled) {
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
//如果已经失败了
if (this.status === rejected) {
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
})
return promise2
}
这个resolvePromise就是promise A+ 规范中说的处理程序,针对这个处理程序,规范中主要是说了以下这几个点:
-
1、x 与 promise 相等
1)如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
2)x 为 Promise:
如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
如果 x 处于执行态,用相同的值执行 promise
如果 x 处于拒绝态,用相同的据因拒绝 promise -
2:x 为对象或函数
把 x.then 赋值给 then
如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
如果 resolvePromise 以值 y 为参数被调用,则运行 [Resolve]
如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
如果 then 不是函数,以 x 为参数执行 promise
如果 x 不为对象或者函数,以 x 为参数执行 promise
下面我们来完成这个resolvePromise :
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
// 如果promise2 和x 相等,那么抛出类型错误
throw new Error('类型错误')
}
//判断x是一个普通值还是一个promise
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
// console.log('x:', x.then());
try {
let then = x.then;
//说明x是一个promise
if (then && typeof then === 'function') {
//执行then方法,this指向X ,成功的回调函数有可能又是一个promise
then.call(x, (y) => {
// y 有可能也是一个promise对象,所以接着处理
resolvePromise(promise2, y, resolve, reject)
}, (r) => {
reject(r)
})
} else {
resolve(x)
}
} catch (e) {
reject(e)
}
} else {
resolve(x)
}
}
这样子我们的链式调用就可以跑起来了,如下:
let x = new MyPromise((res, rej) => {
res(12312)
});
x.then(
(data) => {
console.log("data:", data);
return data
},
(err) => {
console.log("err:", err);
}
).then(res => {
console.log('第二个then:',res) // => 12312
})
当然,日常我们使用的promise,还有很多扩展的方法,像resolve,reject,finally,race,all ,catch等方法,下面我们也来一一的实现一下:
1、resolve方法:
定义:其实就是返回一个成功状态的promise
static resolve(value) {
return new Promise((res, rej) => {
// 直接调用resolve,将状态变为成功态
res(value)
})
}
例子:
let x = MyPromise.resolve(111)
x.then(res =>console.log(res)) // 打印出 111
2、rejected方法:
定义:其实就是返回一个失败状态的promise
static reject(err) {
return new Promise((res, rej) => {
// 直接调用rejext,将状态变为失败态
rej(err)
})
}
3、finally方法
定义:finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作`(摘自es6)
finally(callback) {
callback() // 直接执行
// 调用then方法
return this.then(res => {
return this.resolve(res)
}, err => {
return this.reject(err)
})
}
4、race方法:
定义:Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数(摘自es6)。
static race(args) {
return new Promise((resolve,reject) =>{
for (let i = 0; i < args.length; i++) {
if (isPromise(args[i])) {
args[i].then(res=>{
// 任何一个promise的状态改变了,我们就把外层的promise状态改变,就只会得到第一个状态改变了的promise的返回值
resolve(res)
},err =>{
reject(err)
})
} else {
//如果不是promise 直接成功
resolve(args[i])
}
}
})
}
//判断是否是promise
function isPromise(fn) {
if ((typeof fn === 'object' && fn !== null) || typeof fn === 'function') {
return fn.then && typeof fn.then === 'function'
}
return false
}
例子:
let x3 = new MyPromise(res => {
setTimeout(() => {
res(111)
}, 1000)
})
let x4 = new Promise(res => {
setTimeout(() => {
res(222)
}, 2000)
})
let a = Promise.race([x3, x4, 33])
a.then(dd => {
console.log('dd:', dd) // 打印出 33 如果上面两个promise不加定时器的话,那么应该打印出111
})
5、all 方法:
定义:Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例(摘自es6)。
//promise的all方法
Promise.all = function(promises){
// 返回一个新的promise
return new Promise((resolve,reject)=>{
let arr = [];//返回的数组
let i = 0 ;
let processData = (index,data) =>{
arr[index]= data;
if(++i === promises.length ){
// 如果当前i等于传入数组的长度,那么说明所有的promise项都已经执行完成了
resolve(arr)
}
}
for(let i = 0 ; i< promises.length ; i++){
let current = promises[i];//获得每一项
if(isPromise(current)){
//如果是promise,那么等他的then方法执行的时候将结果放入到新的数组中去
current.then(data =>{
// 按照传入数组的位置将结果放入到新的数组中
processData(i,data)
},reject)
}else {
processData(i,current)
}
}
})
}
6、catch方法:
定义:Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数(摘自es6)。
catch(err) {
return this.then(null, err)
}