前言
Promise也叫期约函数,是ES6中新增的特性,是解决异步编程的一种方案,取代回调函数避免回调地狱。
const p = new Promise((resolve,reject)=>{
resolve(1);
});
// 链式调用
p.then(res => Promise.resolve(res + 2))
.then(res => Promise.resolve(res + 3))
.then(res => console.log(res));
// 6
Promise的一些特性和约定:
- Promise是一个构造函数,接受一个executor函数,executor在new的时候会立即执行,executor接收resolve和reject两个函数作为参数。
- 期约函数有三个状态,pending、fullfiled、reject,初始为pending状态,只能通过resolve和reject改变期约的状态,并且这个过程不可逆。
Promse的实现思路
Promise的核心就是通过resolve/reject函数获取到value/reason,通过then函数获得回调函数,然后将value传入回调函数执行,通过then函数保证执行顺序。具体过程如下:
- 通过resolve/reject获取到value/reason
- 通过then函数获取callback
- 通过then函数保证callback的执行顺序
Promise本质上就是通过then函数将callback串联执行,这点和compose有点类似。
1. Promise构造函数
- 只能通过resolve/reject改变期约函数的状态,状态改变不可逆
- executor函数立即执行
function MyPromise(executor) {
this.value = undefined;
this.reason = undefined;
this.state = 'pending';
this.onResolveCllbacks = [];
this.onRejectCllbacks = [];
const resolve = (val) => {
if (this.state === 'pending') {
this.value = val;
this.state = 'fullfilled';
// 执行保存的回调
if (this.onResolveCllbacks.length > 0) {
this.onResolveCllbacks.forEach(fn => fn(this.value));
this.onResolveCllbacks = [];
};
}
}
const reject = (reason) => {
if (this.state === 'pending') {
this.reason = reason;
this.state = 'reject';
// 执行保存的回调
if (this.onRejectCllbacks.length > 0) {
this.onRejectCllbacks.forEach(fn => fn(this.reason));
this.onRejectCllbacks = [];
};
}
}
executor(resolve, reject);
};
2. Array.prototy.then函数实现
then函数接收onResolve和onReject两个参数作为回调函数,并返回一个新的Promise,返回Promise的规则如下:
- 没有传递onResolve/onReject或者它们没有返回值的时候,直接返回原来的Pomise函数
- onResolve/onReject的返回值是个Promsie的时候直接返回这个Promise
- onResolve/onReject返回值为其它类型的时候,相当于返回Promise.resolve(onResolve())或Promise.reject(onReject())
MyPromise.prototype.then = function (onResolve, onReject) {
if (this.state === 'fullfilled') {
if (typeof onResolve === 'function') {
const result = onResolve?.(this.value);
return result instanceof MyPromise ? result : result ? new MyPromise((resolve, reject) => resolve(result)) : this;
} else {
return this;
};
};
if (this.state === 'reject') {
if (typeof onReject === 'function') {
const result = onReject?.(this.reason);
return result instanceof MyPromise ? result : result ? new MyPromise((resolve, reject) => reject(result)) : this;
} else {
return this;
}
};
// 如果此时还没有通过resolve得到value就存到数组中,等到resolve的时候执行
if (this.state === 'pending') {
return new MyPromise((resolve, reject) => {
typeof onResolve === 'function' && this.onResolveCllbacks.push((value) => {
const result = onResolve(value);
if (result instanceof MyPromise) {
result.then(res => resolve(res), reason => reject(reason));
} else {
resolve(result);
};
});
typeof onReject === 'function' && this.onRejectCllbacks.push((reason) => {
const result = onReject(reason);
if (result instanceof MyPromise) {
result.then(res => resolve(res), reason => reject(reason));
} else {
resolve(result);
};
});
});
then函数的逻辑要考虑两种情况,一种是resolve函数先于then函数执行,这种情况发生在excutor的resolve语句为同步代码:
const p = new MyPromise((resolve,reject)=>resolve('result'));
当executor的resolve包含异步操作时,then函数是先于resolve执行的
const p = new MyPromise((resolve,reject)=>{
setTimeout(()=>{
resolve('result');
},200)
});
-
针对第一种情况直接then函数中直接能获取到resolve/reject的value/reason,然后将其传入onResolve/onReject执行即可。对于第二种情况,要先将onResolve/onReject保存起来,等到resolve/reject获取到value/reason的时候再执行。
-
第二种情况下对于then函数返回的Promsie也要特殊处理,then函数返回Promise是依赖于父级Promise的state和value/reason的,具体的处理方法是直接返回一个新的Promise,把它的resolve和reject方法通过闭包保存下来,等到resolve的时候执行。
onResolveCllbacks和onRejectCllbacks之所以是个数组是因为then函数可以被多次调用,会产生多个回调函数
var p = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 2000);
});
p.then(res=>console.log('第一次调用',res));
p.then(res=>console.log('第二次调用',res));
p.then(res=>console.log('第三次调用',res));
3. 基础使用
3.1 链式调用
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 100);
});
function createPromise(initialValue, value) {
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(initialValue + value)
}, 200);
})
};
p.then(res=>createPromise(res,2))
.then(res=>createPromise(res,3))
.then(res=>console.log(res));
// 6
3.2 执行同步和异步代码
// 同步操作
var p1 = new MyPromise((resolve, reject) => {
resolve(1);
});
p1.then(res=>console.log(res)); //1
// 异步操作
var p2 = new MyPromise((resolve, reject) => {
setTimeout(()=> resolve(1);,100)
});
p2.then(res=>console.log(res)); //1
3.3 多次调用
var p = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 100);
});
p.then(res=>console.log('第一次调用',res)); //第一次调用 1
p.then(res=>console.log('第二次调用',res)); //第二次调用 1
p.then(res=>console.log('第三次调用',res)); //第三次调用 1
4. Array.prototy.catch函数实现
catch函数是onReject的语法糖,onReject与catch同时存在时只会执行onReject。catch函数也返回一个Promise,只不过这个场景很少用,下面的代码中不包含返回Promsie的部分。
MyPromise.prototype.catch = function (onReject) {
if (this.state === 'reject') {
onReject?.(this.reason)
} else {
typeof onReject === 'function' && this.onRejectCllbacks.push(onReject);
};
};
5. Promis.resolve、Promsie.reject的实现
MyPromise.resolve = function (value) {
return new MyPromise((resolve, reject) => resolve(value))
};
MyPromise.reject = function (reason) {
return new MyPromise((resolve, reject) => reject(reason));
};
//使用
const p1 = MyPromise.resolve('result');
p1.then(res => console.log(res)); //result
const p2 = MyPromise.reject('reason');
p2.then(null, reason => console.log(reason)); //reason
6. Promise.all的实现
Promise.all用来执行多个Promise,返回一个新的Promise,返回的这个promise规则如下:
- 所有的promise都成功,Promsie.all才算成功,将它们的结果放到数组中作为新的promise.all的结果
- 有一个promsie失败,Promsie.all就算失败,将第一个出现错误的Promise的信息作为reject的信息
Promise起到的作用是按顺序执行Promise,将它们的结果序列化。
MyPromise.all = function (fns) {
const res = [];
let count = 0;
return new MyPromise((resolve, reject) => {
function next() {
fns[count].then(res => {
if (count === fns.length) {
resolve(res);
} else {
count++
res.push(res);
};
}, reason => {
reject(reason);
});
};
next();
});
};
//使用
var a = new MyPromise((resolve,reject)=>setTimeout(() => {
resolve(1)
}, 200));
var b = new MyPromise((resolve,reject)=>setTimeout(() => {
resolve(2);
}, 100));
var c = new MyPromise((resolve,reject)=>setTimeout(() => {
resolve(3)
}, 300));
var p = Promise.all([a,b,c]);
p.then(res=>console.log(res)); //[1,2,3]
7. Promise.race的实现
race函数接收一个promsie数组进行赛马,返回一个新的promsie,并且这个promsie的结果依赖于先跑完的promise。
MyPromise.race = function (promiseFns) {
return new MyPromise((resolve, reject) => {
promiseFns.forEach((fn) => {
fn.then(res => resolve(res), reason => reject(reason));
});
})
};
// 使用
var a = new MyPromise((resolve,reject)=>setTimeout(() => {
resolve(1)
}, 500));
var b = new MyPromise((resolve,reject)=>setTimeout(() => {
resolve(2);
}, 800));
var c = new MyPromise((resolve,reject)=>setTimeout(() => {
resolve(3)
}, 300));
var p = Promise.race([a,b,c]);
p.then(res=>console.log(res),reason=>console.log(reason)); //3
8. promsie的串行
使用compose串行promsie
function add1(x) {
return x + 1;
};
function add3(x) {
return x + 3;
};
function add5(x) {
return x + 5;
};
const fns = [add1,add3,add5];
const add9 = fns.reduce((promise,cb)=>promise.then(cb),Promise.resolve(1));
add9.then(res=>console.log(res)); //10
//进一步封装
const add = fns => x => fns.reduce((promsie, cb) => promsie.then(cb), Promise.resolve(x));
const add9 = add([add1, add3, add5]);
add9(10).then(res => console.log(res)); //19
9. Promise的不足
- pending状态无法判断是刚开始还是已经结束,状态的变化没有通知
- 一旦创建无法取消
var a = new Promise(()=>{}).then();
console.log(a);
var b = Promise.resolve().then(res=>new Promise(()=>{}));
console.log(b);
promsie无法取消的问题可以通过添加方法手动实现,或者使用一些像cancelPromise这样的三方库,下面的代码利用Promsie.race简单实现了对promsie的中断
function cancelPromsie(promise) {
Promise.race([promise, new Promise((resolve, reject) => { resolve('cancel') })]);
};
var p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p1')
resolve('p1');
}, 1000);
});
cancelPromsie(p1);
Promise的取消本意上是停止掉不需要的异步函数,取消promsie本身没有太大的意义,es6之所以没有支持取消promsie是因为取消promise会使得then函数的链式调用和Promsie.all的执行逻辑更加复杂。