主题
手写Promise.finally resolve reject catch all
前言
上一篇,根据promise/A+规范手写了promise的核心,只包含peomise原型上的then方法,但是promise还有很多的方法平时经常使用。这一篇就来统统的手写一遍
核心代码如下:
js
const STATUS = {
PADDING: "PADDING", // padding
FULFILLED: "FULFILLED", // fulfilled
REJECTED: "REJECTED", // rejected
};
class Promise {
constructor(executor) {
this.status = STATUS.PADDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallBack = [];
this.onRejectedCallBack = [];
const resolve = (val) => {
if (this.status === STATUS.PADDING) {
this.status = STATUS.FULFILLED;
this.value = val;
this.onFulfilledCallBack.forEach((fn) => fn());
}
};
const reject = (reason) => {
if (this.status === STATUS.PADDING) {
this.status = STATUS.REJECTED;
this.reason = reason;
this.onRejectedCallBack.forEach((fn) => fn());
}
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (data) => data;
onRejected = typeof onRejected === "function" ? onRejected : (err) => {throw err;};
let promise2 = new Promise((resolve, reject) => {
if (this.status === STATUS.FULFILLED) {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(x, promise2, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.status === STATUS.REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.status === STATUS.PADDING) {
this.onFulfilledCallBack.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(x, promise2, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallBack.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return promise2;
}
}
module.exports = Promise;
function resolvePromise(x, promise2, resolve, reject) {
if (x === promise2) {
throw TypeError("不允许递归引用同一个promise对象");
}
if ((typeof x === "object" && x !== null) || typeof x === "function") {
let called;
try {
let then = x.then;
if (typeof then === "function") {
then.call( x, function (y) {
if (called) return;
called = true;
resolvePromise(y, promise2, resolve, reject);
},
function (err) {
if (called) return;
called = true;
reject(err);
}
);
} else {
if (called) return;
called = true;
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
reject(error);
}
} else {
resolve(x);
}
}
Promise.resolve 手写
1. 要点
Promise.resolve
是静态方法,默认返回一个成功状态的promise实例,可以调用then方法得到里面的值- 无论
Promise.resolve()
里面传入普通值还是promise,最终都是返回做一个全新的promise - Promise.resolve里面的方法抛出异常或失败,都会将结果传给then的失败或catch
2. 代码实现
js
static resolve(value) {
// Promise.resolve会返回一个全新的promise,所以才能then
return new Promise((resolve, rejevt) => {
resolve(value);
});
}
需要在constroctor里面的resolve方法里面判断一下Promise.resolve里面的value是否是一个promise,若是promise则递归解析
js
const resolve = (val) => {
// val有可能是一个promise,这里需要注意处理
// 若val是一个promise,此时需要调用它的then方法,递归解析
if(val instanceof Promise) {
return val.then(resolve,reject) // then是微任务,因此这里可以拿到reject方法
}
if (this.status === STATUS.PADDING) {
this.status = STATUS.FULFILLED;
this.value = val;
this.onFulfilledCallBack.forEach((fn) => fn());
}
};
本节gitHubCommit记录:实现Promise.resolve
catch 和 Promise.reject 手写
1. 要点
- catch也是promise私有的方法,catch其实就是失败的then,但是没有成功回调。
- 失败的状态没有等待,但是成功是有等待的,这点不一样。
- Promise.reject是静态方法,返回的也是一个promise,只是这个promise是失败的promise。
2. 代码实现
2.1 catch的实现
js
// catch接收一个失败的回调
catch(err) {
return this.then(null, err); // 只有失败,没有成功
}
2.2 Promise.reject的实现
js
static reject(reason) {
return new Promise((resolve, reject) => {
reject(reason); //
});
}
本节githubCommit记录:Promise.reject的实现
finally 手写
声明:==finally==不是try catch finally里面的finally。
1. 要点
- finally是promise原型上的方法,无论成功失败都会执行;
- 方法接收一个回调函数,回调函数里的参数为undefined,也可以不传回调函数;
- filally后面还可以接着then,因为finally返回的也是promise;
- 若finally返回失败的promise或抛异常,下一个then一定走失败,且错误是finally里面的错误或者失败原因;
- 若finally没有返回值,后面的then都只会成功,且成功的值是finally上一个then的成功数据或者失败原因;
2. 代码实现
js
finally(cb) {
return this.then(
data => {
return Promise.resolve(cb()).then(() => data);
},
reason => {js
Promise.resolve(cb()).then(e => {
throw e;
});
}
);
}
Promise.all 手写
1. 要点
- promise.all的作用是实现并发请求
- promise.all接收一个可迭代对象作为参数,不止数组,数组内每一项可以是promise或普通值,最后返回一个promise,调用then方法可以得到一个结果数组,这个数组内每一项顺序于传入的数组对应。
- 只要当数组内有一项失败,则peomise.all最终结果就是失败,全部成功才能成功。
2. 代码实现
js
// Promise.all接受一个可迭代对象作为参数
static all(promises) {
return new Promise((resolve, reject) => {
// 判断promises是否为可迭代对象
if (!isIterable(promises)) {
throw TypeError(
`${promises} is not iterable (cannot read property Symbol(Symbol.iterator))`
);
}
let result = []; // 最终结果
let num = 0;
//处理数据
function processData(i, data) {
result[i] = data;
// 用计数的方式判断全部都成功
if (++num === promises.length) {
resolve(result);
}
}
// 并发请求
for (let i = 0; i < promises.length; i++) {
const item = promises[i];
// 判断item是否为promise
if (isPromise(item)) {
item.then((data) => {
processData(i, data); // 普通值
}, reject);
} else {
// 不是promise
processData(i, item); // 普通值
}
}
});
}
注意: 不能用result.length === promises.length
来判断promise.all全部成功,因为并发请求不一定谁先返回结果,我们通过result[i]
赋值,可能得到的是稀疏数组,不准确,例如
js
let arr = []
arr[3] = 1
console.log(arr.length) // 4
console.log(arr) // [empty × 3, 1]
js
// 判断val是否为可迭代对象
function isIterable(val) {
return val !== null && typeof val[Symbol.iterator] === "function";
}
// 如果一个变量是 Object, 有 then 和 catch 方法, 就认为是 Promise
function isPromise(val) {
return (
val !== null &&
typeof val === "object" &&
typeof val.then === "function" &&
typeof val.catch === "function"
);
}
本节githubCommit记录:Promise.all实现
Promise.allSettled 手写
1. 要点
Promise.allSettled
基本功能与all
一样, 唯一不同的就是,all
只有当所有请求成功后才会成功,allSettled
会等待所有请求都有结果后才会返回一个成功的Promise
,即使有一个或全部失败,都会返回结果allSettled
返回的promise
通过.then
调用后, 也得到一个数组, 但是这个数组每一项都会包裹在一个对象里面, 对象中会标识是成功还是失败, 以及成功值或者失败的原因- 若每一项里有
promise
, 但是这个promise
是padding
状态,那么Promise.allSettled
不会有返回
allSettled返回结果示例
js
Promise.allSettled([...]).then(res=>{
console.log(res)
})
// 输出
[
{ status: 'rejected', reason: 'xxxx' },
{ status: 'fulfilled', value: 4 }
]
2. 代码实现
js
static allSettled(promises) {
return new Promise((resolve, reject) => {
// 判断promises是否是一个可迭代对象
if (!isIterable(promises)) {
throw TypeError(
`${promises} is not iterable (cannot read property Symbol(Symbol.iterator))`
);
}
let result = []; // 结果集合
let num = 0;
function processData(i, data, status) {
if (status === "fulfilled") {
result[i] = { status, value: data };
} else {
result[i] = { status, reason: data };
}
if (++num === result.length) {
resolve(result);
}
}
for (let i = 0; i < promises.length; i++) {
const item = promises[i];
if (isPromise(item)) {
item.then(
res => {
processData(i, res, "fulfilled");
},
err => {
processData(i, err, "rejected");
}
);
} else {
processData(i, item, "fulfilled");
}
}
});
}
本节githubCommit记录:完成allSettled方法
Promise.race 手写
1. 要点
Promise.race
是赛跑的意思,接收一个可迭代对象,返回一个promise
;- 返回的
promise
通过then
可以拿到里面的成功或者失败
2. 代码实现
js
sstatic race(promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
const item = promises[i];
// 用resolve包一下,因为有的项可能不是promise
Promise.resolve(item).then(resolve, reject);
}
});
}
用 race 模拟 abort "中断"Promise
1. 要点
- promise有一个缺点,就是执行后就不能中断,最多就是不要已经开始执行的promise的结果
- ajax上面有一个abort方法,可以终端发出去的请求
- 我们可以利用race的特点,封装一个函数,在请求里面加入一个可以自己控制成功或失败的请求,在发出"中断"指令的时候,我们直接让这个请求失败,那么race就会返回我们自己失败的这个请求,从而达到目的.
2. 代码实现
封装
js
function parce(promise) {
let abort;
let p = new Promise((resolve, reject) => {
abort = reject;
});
parce.abort = abort;
return Promise.race([promise, p]);
}
使用
js
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p1 ok");
}, 3000);
});
parce(p1).then(
res => {
console.log("成功", res);
},
err => {
console.log("错误", err);
}
);
setTimeout(() => {
parce.abort("中断");
}, 1000);
用race实现控制并发请求数量
js
/**
* @function 并发控制方法
* @param {Number} poolLimit 同一时间的最大请求数量
* @param {Array} array 每一次请求的参数组成的数组
* @param {Function} 真正的请求函数
* @returns Promise
*/
function asyncPool(poolLimit, array, iteratorFn) {
const ret = []; // 结果集合
const executing = []; // 请求池
for (const item of array) {
const p = Promise.resolve().then(() => iteratorFn(item));
ret.push(p);
if (poolLimit <= array.length) {
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= poolLimit) {
await Promise.race(executing);
}
}
}
return Promise.all(ret);
}