promise是es6处理异步的一种方案,他可以接受一个异步程序,并且获取到异步程序执行的结果。
首先熟悉Promise的功能与特性,然后根据这些去反推Promise的手写,该文章会手把手带你实现以下过程。
- promise的状态与改变状态的方法
- then方法
- all方法
- race方法
1.状态
- pendding初始状态,fulfiled成功状态,rejected失败状态
- 状态只有两种变化:pending->fulfiled,pending->rejected,且一旦状态改变就会固定,不会再发生改变
- 执行resolve函数可以使Promise的实例状态由pendding->fulfiled;执行reject函数可以使Promise的实例状态由pending->rejected;
js
//实际使用
new Promise((resolve,reject)=>{
resolve("成功")
})
new Promise((resolve,reject)=>{
reject("失败")
})
我们可以观察到创建Promise实例的时候,传入了一个实参executor执行器函数(resolve,reject)=>{ resolve(123) }
,这个executor执行器函数有两个形参resolve,reject。 resolve函数接收一个参数value reject函数接受一个参数resaon 再根据状态的特性,我们可以反推出代码:
js
//手写
const pending = "PENDING";
const fulfiled = "FULFILED";
const rejected = "REJECTED";
class Promise {
constructor(executor) {
this.status = pending;
this.value = undefined;
this.reason = undefined;
const resolve = (value) => {
if (this.status !== pending) return;
this.status = fulfiled;
this.value = value;
};
const reject = (reason) => {
if (this.status !== pending) return;
this.status = rejected;
this.reason = reason;
};
executor(resolve, reject);
}
}
以下是执行结果: 我们可以看到Promise实例的状态,值都发生了改变,并且验证了Promise实例状态只能改变一次的特性。
2.then方法
介绍:
- then方法接受两个参数,onFulfiledCallback函数与onRejectedCallback函数
- 当Promise实例状态变为fulfiled时,执行回调onFulfiledCallback,并将value作为该回调的参数;
- 当Promise实例状态变为rejected时,执行回调onRejectedCallback,并将reason作为该回调的参数 我们先上实际使用then方法代码,以便我们观察特性
js
//实际使用
const p1 = new Promise((resolve, reject) => {
resolve("成功");
}).then(
(value) => {
console.log("////p1", value);
},
(reason) => {
console.log("////p1", reason);
}
);
const p2 = new Promise((resolve, reject) => {
reject("失败");
}).then(
(value) => {
console.log("////p2", value);
},
(reason) => {
console.log("////p2", reason);
}
);
以上代码执行结果:
该结果与我们了解到的then方法特性吻合,接下来我们来手写Promise的then方法
js
const pending = "PENDING";
const fulfiled = "FULFILED";
const rejected = "REJECTED";
class Promise {
constructor(executor) {
this.status = pending;
this.value = undefined;
this.reason = undefined;
const resolve = (value) => {
if (this.status !== pending) return;
this.status = fulfiled;
this.value = value;
};
const reject = (reason) => {
if (this.status !== pending) return;
this.status = rejected;
this.reason = reason;
};
executor(resolve, reject);
}
then(onFulfiledCallback, onRejectedCallback) {
if (this.status === fulfiled) {
onFulfiledCallback(this.value);
}
if (this.status === rejected) {
onRejectedCallback(this.reason);
}
}
}
我们观察代码结合我们的实际使用,会发现一些问题,目前我们考虑的情况都是同步的,如果我们执行的是异步代码呢,then方法还会按照预期执行吗?我们来看看
js
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("成功");
});
}).then((value) => {
console.log("////p1", value);
});
console.log("////p1", p1);
上面的代码会输出什么呢,看看控制台
显然不是我们的预期,我们来分析下:
首先,value为什么没有输出?因为then中的回调没有执行,那为什么Promises实例也变成undefined呢?
那是因为在我们自己的代码执行到then时,then方法中我们没有写返回值,所以就么默认返回undefined啦, 有的同学在这里立马察觉到一丝猫腻--->什么?p1的值不应该是Promise实例吗?这个先不提,我们后面再谈。
现在我们对于异步的情况对我们的then函数做一些改进,先分析现状:
如果resolve异步执行的话,我们在then方法中看到的status肯定就是pending状态,那么我们可以在pending状态时,把then的回调先存起来,等到resolve/reject执行的时候再去执行。好,确定了思路,继续搞起来
js
const pending = "PENDING";
const fulfiled = "FULFILED";
const rejected = "REJECTED";
class Promise {
constructor(executor) {
this.status = pending;
this.value = undefined;
this.reason = undefined;
this.onFulfiledCallbackList=[]
this.onRejectedCallbackList=[]
const resolve = (value) => {
if (this.status !== pending) return;
this.status = fulfiled;
this.value = value;
this.onFulfiledCallbackList.forEach(fn=>fn())
};
const reject = (reason) => {
if (this.status !== pending) return;
this.status = rejected;
this.reason = reason;
this.onRejectedCallbackList.forEach(fn=>fn())
};
executor(resolve, reject);
}
then(onFulfiledCallback, onRejectedCallback) {
if (this.status === fulfiled) {
onFulfiledCallback(this.value);
}
if (this.status === rejected) {
onRejectedCallback(this.reason);
}
if(this.status===pending){
this.onFulfiledCallbackList.push(()=>{
onFulfiledCallback(this.value)
})
this.onFulfiledCallbackList.push(()=>{
onRejectedCallback(this.reason)
})
}
}
}
这里有些同学会有个问题,为什么要把onFulfiledCallback,onRejectedCallback存到数组里? 我们来看以下代码:
js
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("成功");
});
})
p1.then(value=>{
console.log("///我是第一");
})
p1.then(value=>{
console.log("///我是第二");
})
如果我们将onFulfiledCallback,onRejectedCallback简单的复制,那么多次调用then就会覆盖这些回调,所以要存入数组中去挨个执行。
这时候then方法就有雏形了,我们来继续
上面提到一个问题,就是p1的输出是undefind,而不是我们预期的Promise实例,先回顾下代码:
js
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("成功");
});
}).then((value) => {
return 123
});
console.log("////p1", p1);
我们来盘一下then方法的返回值 来大声背一下八股文:
- then方法的返回值由其回调函数的返回值确定,
- 如果回调函数的返回值是一个非Promise实例,则返回一个成功的promise,值为回调函数的返回值。
- 如果回调函数的返回值是一个promise,则then方法返回一个promise,该promise与回调函数返的promise同状态同值。
- 如果回调执行报错,返回一个失败状态的promise,值为error。
好,背完了,记住以后我们来写代码:
js
const pending = "PENDING";
const fulfiled = "FULFILED";
const rejected = "REJECTED";
class Promise {
constructor(executor) {
this.status = pending;
this.value = undefined;
this.reason = undefined;
this.onFulfiledCallbackList = [];
this.onRejectedCallbackList = [];
const resolve = (value) => {
if (this.status !== pending) return;
this.status = fulfiled;
this.value = value;
this.onFulfiledCallbackList.forEach((fn) => fn());
};
const reject = (reason) => {
if (this.status !== pending) return;
this.status = rejected;
this.reason = reason;
this.onRejectedCallbackList.forEach((fn) => fn());
};
executor(resolve, reject);
}
then(onFulfiledCallback, onRejectedCallback) {
return new Promise((resolve, reject) => {
const handle=(cb)=>{
try {
const x = cb(this.value);
if (x instanceof Promise) {
x.then(
(value) => {
resolve(value);
},
(reason) => {
reject(reason);
}
);
} else {
resolve(x);
}
} catch (error) {
reject(error)
}
}
if (this.status === fulfiled) {
handle(onFulfiledCallback)
}
if (this.status === rejected) {
handle(onRejectedCallback)
}
if (this.status === pending) {
this.onFulfiledCallbackList.push(() => {
handle(onFulfiledCallback)
});
this.onFulfiledCallbackList.push(() => {
handle(onRejectedCallback)
});
}
});
}
}
来再看执行结果
因为then方法返回的是promise实例,那说明也可以链式调用了,我们链式调用试试:
js
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("成功");
}, 1000);
})
.then((value) => {
return 123;
})
.then((value) => {
console.log(value, "链式调用成功");
});
执行结果,成功!
3.all方法
先来温习下promise的all方法的特性
- all方法通过Promise构造函数直接调用,这就说明all方法是构造函数的静态方法,不是显式原型里的方法
- all方法也返回一个promise实例。
- all方法接收一个promise实例数组,数组中的promise全部执行成功才会变为fulfiled状态,只要有一个失败就是rejected状态
- fulfiled的值是由传入promise数组成功执行的值组成的数组,且顺序一致,对于非promise实例的成员,值为该成员本身。
- 失败的值是第一个失败的promise成员失败的值
我们根据以上特性来写all方法:
js
static all(pList) {
return new Promise((resolve, reject) => {
const result = [];
let i = 0;
const addResult = (v,index) => {
result[index]=v;
i++;
if (i === pList.length) {
resolve(result);
}
};
pList.forEach((p,index) => {
if (p instanceof Promise) {
p.then(
(v) => {
addResult(v,index);
},
(r) => {
reject(r);
}
);
} else {
addResult(p,index);
}
});
});
}
4.race方法
race方法也是promise的静态方法,接受一个数组,返回一个promise实例,这个实例的状态就是数组中第一个执行完成的promise对象的状态,值也是。 让我们来实现:
js
static race(pList) {
return new Promise((resolve, reject) => {
pList.forEach((p) => {
if (p instanceof Promise) {
p.then((v) => {
resolve(v);
}),
(r) => {
reject(r);
};
} else {
resolve(p);
}
});
});
}