期约基础
ES6新增的引用类型Promise,可以通过new操作符来实例化。 创建新期约时需要传入执行器(executor)函数作为参数。
期约状态机
- 待定(pending)
- 兑现(fulfilled,有时也称“解决”,resolved)
- 拒绝(rejected)
期约最初始状态 期约可以落定(settled)为代表成功的兑现(fulfilled)状态,或代表失败的拒绝(rejected)状态,无论哪种状态都是不可逆的。
期约实例方法
期约实例方法是连接外部同步代码与内部异步代码之间的桥梁。这些方法可以访问异步操作返回的数据,处理期约成功和失败的结果,连续对期约求值,或者添加只有期约进入终止状态时才会执行的代码。
实现Thenable接口
在ES暴露的异步结构中,任何对象都有一个then()方法。
Promise.prototype.then()
是为期约实例添加处理程序的主要方法。这个then()方法接受最多的两个参数:onResolved处理程序和onRejected处理程序。这两个参数都是可选的。
function Promise(executor){
this.status = 'pending';
this.value = null;
this.reason = null;
const resolve = (value) => {
if(this.status === 'pending'){
this.status = 'resolved';
this.value = value;
}
}
const reject = (reason) =>{
if(this.status === 'pending'){
this.stauts = 'rejected';
this.reason = reason;
}
}
executor(resolve,reject);
}
//then
Promise.prototype.then = function(onResolved, onRejected){
onResolved = typeof onResolved === 'function' ? onResolved : data=>data;
onRejected = typeof onRejected === 'function ' ? onRejected : err=>{throw err};
if(this.status === 'resolved'){
onResolved(this.value)
}
if(this.status === 'rejected'){
onRejected(this.reason)
}
}
可以看到,resolve和reject方法中加入了判断,只允许Promise实例状态从pending变为resolved,或从pending变为rejected。
异步实现
但是Promise是用来解决异步问题的,而我们的代码全部都是同步执行的,我们从下面的示例代码入手,逐步分析。
let promise = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('data')
},2000)
})
promise.then(data => {
console.log(data)
})
正常来讲,上述代码会在2s后输出data,但是现在没有输出任何信息,因为我们的实现逻辑是同步的,上述代码在实例化一个Promise构造函数时,会在setTimeout逻辑中调用resolve,也就是2s后才会调用resolve方法,更改Promise实例状态。而结合我们的实现,then方法中的onResolved是同步执行的,它在执行时this.status仍然为pending,并没有做到“2s后在执行onResolved”。
我们应该在开发者调用resolve的时刻去调用onResolved方法,那么应该先在状态为pending时把开发者传进来的onResolved方法存起来,再在resolve方法中执行即可。
function Promise(executor){
this.status = 'pending';
this.value = null;
this.reason = null;
this.onResolvedFunc = Function.prototype;
this.onRejectedFunc = Function.prototype;
const resolve = (value) => {
if(this.status === 'pending'){
this.status = 'resolved';
this.value = value;
this.onResolvedFunc(this.value);
}
}
const reject = (reason) =>{
if(this.status === 'pending'){
this.stauts = 'rejected';
this.reason = reason;
this.onRejectedFunc(this.reason);
}
}
executor(resolve,reject);
}
//then
Promise.prototype.then = function(onResolved, onRejected){
onResolved = typeof onResolved === 'function' ? onResolved : data=>data;
onRejected = typeof onRejected === 'function ' ? onRejected : err=>{throw err};
if(this.status === 'resolved'){
onResolved(this.value)
}
if(this.status === 'rejected'){
onRejected(this.reason)
}
if(this.status === 'pending'){
this.onResolvedFunc = onResolved;
this.onRejectedFunc = onRejected;
}
}
通过测试发现,我们的代码可以支持异步执行了!但是通过下面这个测试样例,你会发现输出顺序有问题。
let promise = new Promise((resolve,reject)=>{
resolve('data')
})
promise.then(data=>{
console.log(data);
})
console.log(1);
//输出结果
//data
//1
正常的话,输出顺序应该是先输出1再输出data。因此,需要将resolve和reject的执行放到任务队列中。这里姑且放到setTimeout中,保证异步执行(这样做法并不严谨,为了保证Promise属于microtasks)
const resolve = (value) => {
setTimeout(() => {
if (this.status === 'pending') {
this.status = 'resolved';
this.value = value;
this.onResolvedFunc(this.value);
}
})
}
const reject = (reason) => {
setTimeout(() => {
if (this.status === 'pending') {
this.stauts = 'rejected';
this.reason = reason;
this.onRejectedFunc(this.reason);
}
})
}
executor(resolve, reject);
//输出结果
//1
//data
Promise细节完善
为Promise添加多个then方法,执行以下代码块:
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('data')
},2000)
})
promise.then(data => {
console.log(`1:${data}`);
})
promise.then(data => {
console.log(`2:${data}`);
})
而我们的实现只会输出 2:data,这是因为第二个then方法中的onResolvedFunc会覆盖第一个then方法中的onResolvedFunc。
我们只需要将所有then方法中的onResolvedFunc储存到一个数组onResolvedArray中,在当前Promise被决议时依次执行onResolvedArray数组内的方法即可。对于onRejectedFunc同理,实现代码如下:
function Promise(executor) {
this.status = 'pending';
this.value = null;
this.reason = null;
this.onResolvedArray = [];
this.onRejectedArray = [];
const resolve = (value) => {
setTimeout(() => {
if (this.status === 'pending') {
this.status = 'resolved';
this.value = value;
this.onResolvedArray.forEach(func => {
func(value)
})
}
})
}
const reject = (reason) => {
setTimeout(() => {
if (this.status === 'pending') {
this.stauts = 'rejected';
this.reason = reason;
this.onRejectedArray.forEach(func => {
func(reason)
})
}
})
}
executor(resolve, reject);
}
//then
Promise.prototype.then = function (onResolved, onRejected) {
onResolved = typeof onResolved === 'function' ? onResolved : data => data;
onRejected = typeof onRejected === 'function ' ? onRejected : err => { throw err };
if (this.status === 'resolved') {
onResolved(this.value)
}
if (this.status === 'rejected') {
onRejected(this.reason)
}
if (this.status === 'pending') {
this.onResolvedArray.push(onResolved);
this.onRejectedArray.push(onRejected);
}
}
另一个需要完善的细节是,在构造函数中如果出错,将会自动出发Promise实力状态变为rejected,因此我们用try...catch块对executor进行包裹,如下:
try {
executor(resolve, reject);
} catch (error) {
reject(error)
}
到目前为止,已经初步实现了基本的Promise,得出下面一些重要结论:
- Promise的状态具有凝固性。
- Promise可以在then方法第二个参数中进行错误处理。
- Promise实例可以添加多个then处理场景。
Promise then的链式调用
我们先来看一道题目,判断以下代码输出结果:let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('carol')
},2000)
})
promise.then(data => {
console.log(data);
return `${data} next then`
}).then(data => {
console.log(data);
})
这段代码执行后,将会在2s后输出carol,紧接着输出carol next then。Promise实例的then方法支持链式调用。
如果在第一个then方法体的onResolved函数中返回另一个Promise实例结果又是怎样的呢?来看以下代码:
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('carol')
},2000)
})
promise.then(data => {
console.log(data);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`${data} next then`)
},4000)
})
})
.then(data => {
console.log(data);
})
上述代码将在2s后输出carol,紧接着再过4s(第6s)输出carol next then。
由此可知,一个Promise实例then方法的onResolved函数和onRejected函数是支持再次返回一个Promise实例的,也支持返回一个非Promise实例的普通值;并且,返回的这个实例/普通值将会传给下一个then方法的onResolved或onRejected。
链式调用初步实现
为了能够支持then方法的链式调用,每一个then方法的onResolved函数和onRejected函数都应该返回一个Promise实例。先来看一个实际使用Promise链的场景,代码如下:
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('carol')
},2000)
})
promise.then(data => {
console.log(data);
return `${data} next then`
}).then(data => {
console.log(data);
})
这种onResolved函数会返回一个普通字符串类型的基本值,这里的onResolved函数代码如下:
data => {
console.log(data);
return `${data} next then`
}
在前面实现的then方法中,我们可以创建一个新的Promise实例即promise2,并最终将这个promise2返回,代码如下:
Promise.prototype.then = function (onResolved, onRejected) {
onResolved = typeof onResolved === 'function' ? onResolved : data => data;
onRejected = typeof onRejected === 'function ' ? onRejected : err => { throw err };
//将promise2作为then方法返回值
let promise2;
if (this.status === 'resolved') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
const result = onResolved(this.value);
resolve(result)
} catch (error) {
reject(error)
}
})
})
}
if (this.status === 'rejected') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
const result = onRejected(this.reason);
resolve(result)
} catch (error) {
reject(error)
}
})
})
}
if (this.status === 'pending') {
return promise2 = new Promise((resolve, reject) => {
this.onResolvedArray.push(() => {
try {
const result = onResolved(this.value);
resolve(result)
} catch (error) {
reject(error)
}
});
this.onRejectedArray.push(() => {
try {
const result = onRejected(this.reason);
resolve(result)
} catch (error) {
reject(error)
}
});
})
}
}
这里重点理解this.status==='pending'判断分支中的逻辑。当使用Promise实例调用其then方法时,应该返回一个Promise实例即promise2,它在异步处理结束后,依次执行onResolvedArray或onRejectedArray数组中的函数时被决议。
链式调用完善实现
我们继续实现then方法,来显示返回一个Promise实例,实现代码如下:let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('carol')
},2000)
})
promise.then(data => {
console.log(data);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`${data} next then`)
},4000)
})
})
.then(data => {
console.log(data);
})
在之前实现的result语句中,使变量result既可以是一个普通值,也可以是一个Promise实例,为此我们抽象出resolvePromise方法进行统一处理,resolvePromise函数接收以下4个参数:
- promise2: 返回的Promise实例。
- result: onResolved或onRejected函数的返回值。
- resolve: promise2的resolve方法。
- reject: promise2的reject方法。 改动后的代码如下:
function Promise(executor) {
this.status = 'pending';
this.value = null;
this.reason = null;
this.onResolvedArray = [];
this.onRejectedArray = [];
const resolve = (value) => {
setTimeout(() => {
if (this.status === 'pending') {
this.status = 'resolved';
this.value = value;
this.onResolvedArray.forEach(func => {
func(value)
})
}
})
}
const reject = (reason) => {
setTimeout(() => {
if (this.status === 'pending') {
this.stauts = 'rejected';
this.reason = reason;
this.onRejectedArray.forEach(func => {
func(reason)
})
}
})
}
try {
executor(resolve, reject);
} catch (error) {
reject(error)
}
}
const resolvePromise = (promise2, result, resolve, reject) => {
//当result和promise2相等时,也就是在onResolved返回promise2时,执行reject
//对“死循环”进行处理
if(result === promise2){
reject(new TypeError('error due to circular reference'))
}
//是否已经执行过onResolved或onRejected
let consumed = false;
let thenable;
if(result instanceof Promise){
if(result.status === 'pending'){
result.then(function(data){
resolvePromise(promise2, data, resolve, reject)
},reject)
}else{
result.then(resolve,reject)
}
return
}
let isComplexResult = target => (typeof target === 'object' || typeof target === 'function') && target !== null;
//如果返回的是疑似Promise类型
if(isComplexResult(result)){
try {
thenable = result.then;
//判断返回值是否是Promise类型
if(typeof thenable === 'function'){
thenable.call(result,function(data){
if(consumed) return;
consumed = true;
return resolvePromise(promise2, data, resolve, reject)
},function(error){
if(consumed) return;
consumed = true;
return reject(error)
})
}else{
resolve(result)
}
} catch (error) {
if(consumed) return;
consumed = true;
return reject(error)
}
}else{
resolve(result)
}
}
//then
Promise.prototype.then = function (onResolved, onRejected) {
onResolved = typeof onResolved === 'function' ? onResolved : data => data;
onRejected = typeof onRejected === 'function ' ? onRejected : err => { throw err };
//将promise2作为then方法返回值
let promise2;
if (this.status === 'resolved') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
const result = onResolved(this.value);
resolvePromise(promise2, result, resolve, reject)
} catch (error) {
reject(error)
}
})
})
}
if (this.status === 'rejected') {
return promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
const result = onRejected(this.reason);
resolvePromise(promise2, result, resolve, reject)
} catch (error) {
reject(error)
}
})
})
}
if (this.status === 'pending') {
return promise2 = new Promise((resolve, reject) => {
this.onResolvedArray.push(() => {
try {
const result = onResolved(this.value);
resolvePromise(promise2, result, resolve, reject)
} catch (error) {
reject(error)
}
});
this.onRejectedArray.push(() => {
try {
const result = onRejected(this.reason);
resolvePromise(promise2, result, resolve, reject)
} catch (error) {
reject(error)
}
});
})
}
}
Promise穿透实现
先看以下代码:let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('carol')
},2000)
})
promise.then(null)
.then(data => {
console.log(data);
})
这段代码会在2s后输出carol,这就是Promise穿透现象:给then()函数传递非函数值作为其参数时,实际上会被解析成then(null),这时上一个Promise对象的决议结果便会“穿透”到下一个then方法的参数中。
在then()方法实现中,已经为onResolved和onRejected函数加上了如下判断:
Promise.prototype.then = function(onResolved, onRejected){
onResolved = typeof onResolved === 'function' ? onResolved : data => data;
onRejected = typeof onRejected === 'function ' ? onRejected : err => { throw err };
//...
}
如果onResolved不是函数类型,则给一个默认值,该默认值是返回其参数的函数,这就实现了“穿透”的作用。
Promise静态方法和其他方法实现
- Promise.prototype.catch
- Promise.resolve
- Promise.reject
- Promise.all
- Promise.race
Promise.resolve
Promise.resolve(value)方法返回一个以给定值解析后的Promise实例对象。Promise.resolve = function(value) => {
return new Promise((resolve,reject) => {
resolve(value)
})
}
同样实现一个Promise.reject(reason),代码如下
Promise.resolve = function(reason) => {
return new Promise((resolve,reject) => {
reject(reason)
})
}
Promise.all
Promise.all(iterable)方法返回一个Promise实例,此实例在iterable参数内所有Promise实例都“完成”(resolved)或参数中不包含Promise实例时完成回调(resolve);如果参数中的Promise实例有一个失败(rejected),则此实例回调失败(reject)。先来看一个实例:
//promise.all
const promise1 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve("carol")
},2000)
})
const promise2 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve("carol")
},2000)
})
Promise.all([promise1,promise2]).then(data => {
console.log(data);
})
//输出结果
//['carol', 'carol']
实现思路:
Promise.all = function(promiseArray){
if(!Array.isArray(promiseArray)){
throw new TypeError('The arguments should be an array!')
}
return new Promise((resolve,reject) => {
try {
let resultArray = [];
const length = promiseArray.length;
for(let i=0;i<length;i++){
promiseArray[i].then(data => {
resultArray.push(data)
if(resultArray.length === length){
resolve(resultArray)
}
},reject)
}
} catch (error) {
reject(error)
}
})
}
先对参数promiseArray的类型进行判断,对非数组类型进行抛错。Promise.all会返回一个Promise实例,这个实例会在promiseArray中的所有Promise实例被决议后进行决议,决议结果是一个数组,存有所有Promise实例的决议值。
Promise.race
先来看一个实例:const promise1 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve("carol1")
},2000)
})
const promise2 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve("carol2")
},4000)
})
Promise.race([promise1,promise2]).then(data => {
console.log(data);
})
//输出结果(2s后)
//carol1
实现Promise.race的代码如下:
Promise.race = function(promiseArray){
if(!Array.isArray(promiseArray)){
throw new TypeError('The arguments should be an array!')
}
return new Promise((resolve,reject) => {
try {
const length = promiseArray.length;
for(let i=0;i<length;i++){
promiseArray[i].then(resolve,reject)
}
} catch (error) {
reject(error)
}
})
}
这里是用for循环同步执行PromiseArray数组中所有Promise实例的then方法,第一个resolve的实例会直接触发新的Promise实例的resolve方法。
《前端开发核心知识进阶》——promise总结