谈谈promise
promise是es6提供的异步编程解决方案,相比之前的回调函数方案更加合理强大。
promise是一种异步流程的控制手段,下面几个promise特点
1、避免了回调地狱,可链式调用。
2、支持多个并发请求,获取并发请求数据。
3、promise可以解决异步的问题,但不能说promise本身就是异步的。
4、promise三种状态:pending,resolved,rejected,状态只能是pending到resolved或者pending到rejected,不能是resolved到rejected或者rejected到resolved。状态一旦改变就不会再变,且状态不可逆。
5、Promise对象是一个构造函数,只有一个参数叫executor执行器,默认new的时候就会调用。
promise缺点:
1、无法取消Promise,一旦新建它就会执行,无法中途取消。
2、如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
3、当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
promise雏形
先写MyPromise类的构造函数,里面有的私有成员
state ------ Promise当前状态
value ------ 最终我们要的执行结果(数据)
Promise第一个参数(回调函数)立即执行,是同步的,真正异步的在then()
class MyPromise{
constructor(executor){
this.state = 'pending';
this.value = null;
//resolve函数
let resolve = result => {
if(this.state !== 'pending') return; //状态只能从等待态到成功态
this.state = 'fulfilled';
this.value = result;
}
//reject函数
let reject = reason => {
if(this.state !== 'pending') return; //状态只能从等待态到失败态
this.state = 'rejected';
this.value = reason;
}
//executor立即执行,如果执行出错则走reject
try{
executor(resolve, reject);
}catch(err){
reject(err);
}
}
}
then()
上面我们知道,Promise有执行结果会调用resolve,执行出错会调用reject。但真正拿到执行结果再进行处理的在then()。
then(successFn, failFn) ,有两个参数,我简单粗暴的以successFn、failFn命名,当resolve被调用时会执行successFn,而reject被调用时hi执行failFn。
所以JS代码执行到then()实际只是将successFn和failFn存储起来,静静等待被调用。
所以在构造函数里加两个容器用于存放successFn和failFn
class MyPromise{
constructor(executor){
...
this.successQueue = [];
this.failQueue = [];
//resolve函数
let resolve = result => {
...
this.successQueue.forEach(item => {
item(this.value)
})
}
//reject函数
let reject = reason => {
...
this.failQueue.forEach(item => {
item(this.value);
})
}
}
then(successFn, failFn){
this.successQueue.push(successFn);
this.failQueue.push(failFn);
}
}
到这里就简单的实现了then(),现在可以简单的使用。但有一个问题,如果Promise里没有进行一步操作而是直接resolve或reject的话,上面的封装可能会出现问题,比如:
new MyPromise((resolve, reject) => {
resolve('haha')
}).then(res => {
console.log(res)
}, err => {
console.log(err)
})
结果没有任何输出,这是因为我们上面的封装,在resolve执行时,then的successFn和failFn都还没放进容器里。
实际上then里的successFn和failFn始终是异步执行的,也就是微任务。这里我们可以用setTimeout包一下,达成那种效果
class MyPromise{
constructor(executor){
...
//resolve函数
let resolve = result => {
...
let timer = setTimeout(() => {
clearTimeout(timer);
this.successQueue.forEach(item => {
item(this.value)
})
}, 0)
}
//reject函数
let reject = reason => {
...
let timer = setTimeout(() => {
clearTimeout(timer);
this.failQueue.forEach(item => {
item(this.value)
})
}, 0)
}
}
...
}
链式then()
说到链式调用,不用想,then()肯定是返回一个Promise。注意点:
第一个then的successFn或failFn执行有结果会走到第二个then的successFn,第一个then的successFn或failFn执行出错会走到第二个then的failFn。如此类推
class MyPromise{
constructor(executor){
...
}
then(successFn, failFn){
return new MyPromise((resolve, reject) => {
//在successFn和failFn外再包一层函数,这样resolve()或reject()时实际会执行该函数
//在该函数里判断当前successFn或failFn的结果,然后再resolve()或reject()给下一个then
this.successQueue.push(() => {
try{
let x = successFn(this.value);
//如果successFn返回一个Promise,则执行该Promise的then方法即可
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
}catch(err){
reject(err);
}
})
this.failQueue.push(() => {
try{
let x = failFn(this.value);
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
}catch(err){
reject(err);
}
})
})
}
}
到这里还要考虑一个问题,就是then方法参数默认值问题,如果then方法中没有传参的话,事实上要将promise执行结果传到下一个then处理。所以then方法中的successFn和failFn应该有默认值,successFn默认值是一个有返回值的函数。而failFn需要注意,我们要想在下一个then的failFn处理错误的话,那么当前的failFn就必须reject。
then(successFn, failFn){
typeof successFn !== 'function' ? successFn = result => result : null;
return new MyPromise((resolve, reject) => {
typeof failFn !== 'function' ? failFn = reason => reject(reason) : null;
...
})
}
catch()
catch方法原理极其简单,比如Promise出错,第一个then(successFn, null),第二个then(null, failFn),没错,catch方法就是第二个then,一模一样。
catch(failFn){
return this.then(null, failFn);
}
Promise.all()
原理也不难,也是返回一个Promise,只不过这个Promise里的异步操作是等一串Promise任务都完成了才resolve,而如果其中有一个出错了,那就reject
static all(promiseArr = []){
return new MyPromise((resolve, reject) => {
let n = 0;
let result = [];
let errFlag = false;
for(let i=0; i<promiseArr.length; i++){
//如果有一个Promise出错了,那就结束循环
if(errFlag) break;
promiseArr[i].then(res => {
n++;
result[i] = res;
//如果结果数量等于promiseArr.length,那就resolve
if(n === promiseArr.length)} resolve(result);
}, err => {
errFlag = true;
reject(err);
})
}
})
}
至此,大致完成了自己封装的Promise,肯定没有原生的完善,基本功能都实现了。
完整代码
class MyPromise{
constructor(executor){
this.state = 'pending';
this.value = null;
this.successQueue = [];
this.failQueue = [];
let resolve = result => {
if(this.state !== 'pending') return;
// then中的函数总是异步执行的
this.state = 'fulfilled';
this.value = result;
let timer = setTimeout(() => {
clearTimeout(timer);
this.successQueue.forEach(item => {
item(this.value);
})
}, 0)
}
let reject = reason => {
if(this.state !== 'pending') return;
// then中的函数总是异步执行的
this.state = 'rejected';
this.value = reason;
let timer = setTimeout(() => {
clearTimeout(timer);
this.failQueue.forEach(item => {
item(this.value);
})
}, 0);
}
// 执行器立即执行
try{
executor(resolve, reject);
}catch(err){
//如果一开始执行器出错就走reject
reject(err);
}
}
then(successFn, failFn){
typeof successFn !== 'function' ? successFn = result => result : null;
return new MyPromise((resolve, reject) => {
this.successQueue.push(() => {
try{
let x = successFn(this.value);
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
}catch(err){
reject(err);
}
});
typeof failFn !== 'function' ? failFn = reason => reject(reason) : null;
this.failQueue.push(() => {
try{
let x = failFn(this.value);
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
}catch(err){
reject(err);
}
});
})
}
catch(failFn){
return this.then(null, failFn);
}
static all(promiseArr){
return new MyPromise((resolve, reject) => {
let result = [];
let n = 0;
let errFlag = false;
for(let i=0; i<promiseArr.length; i++){
if(errFlag) break;
promiseArr[i].then(res => {
result[i] = res;
n++;
if(n === promiseArr.length){
resolve(result);
}
}, err => {
errFlag = true;
reject(err);
})
}
})
}
}