Promise的特性及实现原理
Promise对象的特性
要实现Promise
对象首先我们要了解Promise
拥有哪些特性,简单概括为以下几点
1、Promise
有三种状态:pending
(进行中)、fulfilled
(已成功)、rejected
(已失败)
2、Promise
对象接受一个回调函数作为参数, 该回调函数接受两个参数,分别是成功时的回调resolve
和失败时的回调reject
;另外resolve
的参数除了正常值以外, 还可能是一个Promise
对象的实例;reject
的参数通常是一个Error
对象的实例。
3、then
方法返回一个新的Promise
实例,并接收两个参数onResolved
(fulfilled
状态的回调);
onRejected
(rejected
状态的回调,该参数可选)
4、catch
方法返回一个新的Promise
实例
5、finally
方法不管Promise
状态如何都会执行,该方法的回调函数不接受任何参数
6、Promise.all()
方法将多个多个Promise
实例,包装成一个新的Promise
实例,该方法接受一个由Promise
对象
组成的数组作为参数(Promise.all()
方法的参数可以不是数组,但必须具有Iterator
接口,且返回的每个成员都是Promise
实例),注意参数中只要有一个实例触发catch
方法,都会触发Promise.all()
方法返回的新的实例的catch
方法,如果参数中的某个实例本身调用了catch
方法,将不会触发Promise.all()
方法返回的新实例的catch
方法
7、Promise.race()
方法的参数与Promise.all
方法一样,参数中的实例只要有一个率先改变状态就会将该实例的状态传给Promise.race()
方法,并将返回值作为Promise.race()
方法产生的Promise
实例的返回值
8、Promise.resolve()
将现有对象转为Promise
对象,如果该方法的参数为一个Promise
对象,Promise.resolve()
将不做任何处理;如果参数thenable对象(即具有then方法),Promise.resolve()
将该对象转为Promise
对象并立即执行then
方法;如果参数是一个原始值,或者是一个不具有then
方法的对象,则Promise.resolve
方法返回一个新的Promise
对象,状态为fulfilled
,其参数将会作为then
方法中onResolved
回调函数的参数,如果Promise.resolve
方法不带参数,会直接返回一个fulfilled
状态的 Promise
对象。需要注意的是,立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
9、Promise.reject()
同样返回一个新的Promise
对象,状态为rejected
,无论传入任何参数都将作为reject()
的参数
以上就是Promise对象的一些基本特性,下面我们根据Promise
对象的特性,自己来实现一个简单的Promise对象
第一步、Promise
对象用三种状态分别是:pending、fulfilled、rejected。
当resolve
的参数为一个Promise
对象的实例的时时候该实例的resolve
的参数即为外层Promise对象then方法中onResolved方法的参数,reject的参数即为外层Promise对象then方法(或catch方法)中onRejected方法的参数
function timeout2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
a // 此处a未定义,如果注释掉这里即正常执行
resolve(200)
} catch (e) {
reject(e)
}
}, 1000)
})
}
function timeout(time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
resolve(timeout2())
} catch (e) {
reject(e)
}
}, time)
})
}
let p = timeout(1000);
p.then(res => {
console.log(res); // 200
}).catch(err => {
console.log(err); // ReferenceError: a is not defined
})
由上面的例子我们可以定义一个基本框架:
/*
Promise构造函数接收一个executor函数, executor函数执行完同步或异步操作后,调用它的两个参数resolve和reject
如果操作成功,调用resolve并传入value
如果操作失败,调用reject并传入reason
*/
class MyPromise {
constructor(executor) {
if(typeof executor !== 'function') {
throw new Error('MyPromise must accept a function as a parameter')
}
// Promise当前的状态
this.status = 'pending'
// Promise的值
this.data = undefined
// Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
this.onResolvedCallback = []
// Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
this.onRejectedCallback = []
/*
考虑到执行executor的过程中有可能出错,所以我们用try/catch块给包起来,
并且在出错后以catch到的值reject掉这个Promise,另外因为resolve和reject在外部调用故需要绑定this
*/
try {
executor(this.resolve.bind(this), this.reject.bind(this))
} catch (err) {
this.reject(err)
}
}
resolve(value) {
// 成功时将状态改为fulfilled
if(this.status === 'padding') {
// 如果传入的值是一个promise实例,则必须等待该Promise对象状态改变后,
// 当前Promsie的状态才会改变,且状态取决于参数Promsie对象的状态
if(value instanceof MyPromise) {
value.then(res => {
this.data = res
this.status = 'fulfilled'
//执行resolve的回调函数,将value传递到callback中
this.onResolvedCallback.forEach(callback => callback(res))
}, err => {
this.data = err
this.status = 'rejected'
//执行reject的回调函数,将reason传递到callback中
this.onRejectedCallback.forEach(callback => callback(err))
})
} else {
this.status = 'fulfilled';
this.data = value;
//执行resolve的回调函数,将value传递到callback中
this.onResolvedCallback.forEach(callback => callback(value))
}
}
}
reject(reason) {
// 失败时将状态改为rejected
if(this.status === 'padding') {
this.status = 'rejected'
this.data = reason;
// 触发所有的回调函数
this.onRejectedCallback.forEach(item => {
item(reason)
})
}
}
}
第二步、实现then方法
Promise
实例的then
方法返回一个新的Promise
实例,并接收两个参数onResolved
(fulfilled状态的回调); onRejected
(rejected状态的回调,该参数可选);
// then方法接收两个参数,onResolved,onRejected,分别为Promise成功或失败后的回调
class MyPromise {
// ....
then(onResolved, onRejected) {
// 根据标准,如果then的参数不是function,则我们需要忽略它,此处以如下方式处理
onResolved = typeof onResolved === 'function' ? onResolved : value => {}
onRejected = typeof onRejected === 'function' ? onRejected : reason => {}
if (this.status === 'fulfilled') {
return new MyPromise((resolve, reject) => {
})
}
if (this.status === 'rejected') {
return new MyPromise((resolve, reject) => {
})
}
if (this.status === 'pending') {
return new MyPromise((resolve, reject) => {
})
}
}
}
当我们在链式调用Promise
实例的时候,当一个实例的then方法返回另一个实例,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为fulfilled,就调用第一个回调函数(即onResolved),如果状态变为rejected,就调用第二个回调函数(即onRejected)。
let p1 = new Promise((resolve,reject) => {
setTimeout(function(){
try {
resolve(1)
} catch (e) {
reject(e)
}
}, 100)
})
let p2 = new Promise((resolve,reject) => {
setTimeout(function(){
try {
resolve(2)
} catch (e) {
reject(e)
}
}, 100)
})
p1.then(res => {
console.log(res); // 1
return p2
}).then(res => {
console.log(res); // 2
}).catch(err => {
})
第三步、完善then方法
根据上面的例子我们来补充then方法里面的内容
// then方法接收两个参数,onResolved,onRejected,分别为Promise成功或失败后的回调
class MyPromise {
// ....
then(onResolved, onRejected) {
// 根据标准,如果then的参数不是function,则我们需要忽略它,此处以如下方式处理
onResolved = typeof onResolved === 'function' ? onResolved : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => reason
// 如果promise1(此处即为this)的状态已经确定并且是resolved,我们调用onResolved
// 因为考虑到有可能throw,所以我们将其包在try/catch块里
if (this.status === 'fulfilled') {
return new MyPromise((resolve, reject) => {
try {
let result = onResolved(this.data)
// 如果onResolved的返回值是一个Promise对象,直接取它的结果做为返回promise实例的结果
if(result instanceof MyPromise) {
result.then(resolve, reject)
}
resolve(result) // 否则,以它的返回值做为返回promise实例的结果
} catch (e) {
reject(e)
}
})
}
// rejected状态的处理方法与上面基本一致
if (this.status === 'rejected') {
return new MyPromise((resolve, reject) => {
try {
let result = onRejected(this.data)
// 如果onRejected的返回值是一个Promise对象,直接取它的结果做为返回promise实例的结果
if(result instanceof MyPromise) {
result.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
}
if (this.status === 'pending') {
/**
* 如果当前的Promise还处于pending状态,我们并不能确定调用onResolved还是onRejected,
* 只能等到Promise的状态确定后,才能确实如何处理。
* 所以我们需要把以上两种情况的处理逻辑做为callback放入promise1(此处即this)的回调数组里
* 具体逻辑也与上面类似
*/
return new MyPromise((resolve, reject) => {
this.onResolvedCallback.push((value) => {
try {
let result = onResolved(this.data);
if (result instanceof MyPromise) {
result.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
this.onRejectedCallback.push(reason => {
try {
let result = onRejected(this.data)
if (result instanceof MyPromise) {
result.then(resolve, reject)
}
} catch (e) {
reject(e)
}