对Promise不熟悉的可以先看一下下列文章,本文代码出处均来源下列文章
【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理) - 掘金 (juejin.cn)
从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节 - 掘金 (juejin.cn)
字节飞书面试——请实现promise.all - 掘金 (juejin.cn)
1. 使用 Promise 实现每隔1秒输出1,2,3
原出处: 【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理) - 掘金 (juejin.cn)
const arr = [1, 2, 3];
arr.reduce((pre, value) => {
return pre.then(() => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(value);
resolve(value);
}, 1000);
});
});
}, Promise.resolve());
2. 使用 Promise 实现红绿灯交替重复亮
原出处: 【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理) - 掘金 (juejin.cn)
function red() {
console.log("red");
}
function green() {
console.log("green");
}
function yellow() {
console.log("yellow");
}
// 亮灯函数
const light = function (timer, callback) {
return new Promise((resolve) => {
setTimeout(() => {
callback();
resolve();
}, timer);
});
};
const step = function () {
Promise.resolve()
.then(() => {
return light(3000, red);
})
.then(() => {
return light(2000, green);
})
.then(() => {
return light(1000, yellow);
})
.then(() => {
return step();
});
};
step();
3. 实现 mergePromise 函数
原出处: 【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理) - 掘金 (juejin.cn)
const time = (timer) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, timer);
});
};
const ajax1 = () =>
time(2000).then(() => {
console.log(1);
return 1;
});
const ajax2 = () =>
time(1000).then(() => {
console.log(2);
return 2;
});
const ajax3 = () =>
time(1000).then(() => {
console.log(3);
return 3;
});
function mergePromise(arr) {
const res = new Array(arr.length);
let promise = Promise.resolve();
arr.forEach((ajax, index) => {
promise = promise.then(async () => {
const value = await ajax();
res[index] = value;
return value;
});
});
return promise.then(() => {
return res;
});
}
mergePromise([ajax1, ajax2, ajax3]).then((data) => {
console.log("done");
console.log(data); // data 为 [1, 2, 3]
});
// 要求分别输出
// 1
// 2
// 3
// done
// [1, 2, 3]
4. 限制异步操作的并发个数并尽可能快的完成全部
原出处: 【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理) - 掘金 (juejin.cn)
思路:初始化一个长度为3的promises,用Promise.race(promises)获取返回最快的promise,按顺序取出未执行队列生成promise。将最早完成的promise代替,并对新生成的数组再次进行Promise.race(promises),重复上述步骤,为了便于理解,我们先写一段简易版代码
// dataArr:需要执行的参数数组,这里先默认长度为8。handler:执行的函数
function limitLoad(dataArr, handler) {
let sequence = [].concat(dataArr); // 复制数据数组
// 初始化一个长度为3的promises
let promises = sequence.splice(0, 3).map((data, index) => {
return handler(data).then(() => {
// 完成后,返回下标
return index;
});
});
return Promise.race(promises)
.then((fastestIndex) => {
// 获取到已经完成的下标,将"容器"内已经完成的那一项替换
promises[fastestIndex] = handler(dataArr[0]).then(() => {
return fastestIndex; // 要继续将这个下标返回,以便下一次确定完成下标
});
})
.then(() => {
// 对新生成的promises进行新一轮race
return Promise.race(promises);
})
.then((fastestIndex) => {
// 按顺序取出未完成的对已完成的进行替换
promises[fastestIndex] = handler(dataArr[1]).then(() => {
return fastestIndex;
});
})
.then(() => {
// 对新生成的promises进行新一轮race
return Promise.race(promises);
})
.then((fastestIndex) => {
// 重复代码,只有取出数据位置依次递增
promises[fastestIndex] = handler(dataArr[2]).then(() => {
return fastestIndex;
});
})
.then(() => {
return Promise.race(promises);
})
.then((fastestIndex) => {
promises[fastestIndex] = handler(dataArr[3]).then(() => {
return fastestIndex;
});
})
.then(() => {
return Promise.race(promises);
})
.then((fastestIndex) => {
promises[fastestIndex] = handler(dataArr[4]).then(() => {
return fastestIndex;
});
})
.then(() => {
// 最后三个用.all来调用
return Promise.all(promises);
});
}
理解上述代码后,我们对其进行优化
function limitConcurrence(promiseArr, limit) {
const res = new Array(promiseArr.length);
let sequence = [].concat(promiseArr); // 复制promise数组
// 这一步是为了初始化 promises 这个"容器"
let promises = sequence
.splice(0, Math.min(limit, promiseArr.length))
.map((promise, index) => {
return promise().then((value) => {
res[index] = value;
// 返回下标是为了知道数组中是哪一项最先完成
return index;
});
});
// 注意这里要将整个变量过程返回,这样得到的就是一个Promise,可以在外面链式调用
return sequence
.reduce((pCollect, promise, pIndex) => {
return pCollect
.then(() => {
return Promise.race(promises); // 返回已经完成的下标
})
.then((fastestIndex) => {
// 获取到已经完成的下标
// 将"容器"内已经完成的那一项替换
promises[fastestIndex] = promise().then((value) => {
res[pIndex + limit] = value;
return fastestIndex; // 要继续将这个下标返回,以便下一次变量
});
})
.catch((err) => {
Promise.reject(err);
});
}, Promise.resolve()) // 初始化传入
.then(() => {
// 最后三个用.all来调用
return Promise.all(promises).then(() => {
return res;
});
});
}
5. 实现一个简单的 Promise
原出处: 从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节 - 掘金 (juejin.cn)
原出处实现更加完善,更多细节可查看原文
使用 queueMicrotask 创建微任务
思路:
promise是一个类- 字段
status标识运行状态,运行状态一旦改变不可逆转value保存成功后的值reason保存失败的原因onFulfilledCallback保存成功后回调函数onRejectedCallback保存失败后回调函数
- 函数
resolve函数- 接受参数
val,将val赋值给value保存 - 修改
status状态为成功fulfilled - 调用成功后的回调函数
onFulfilledCallback
- 接受参数
reject函数- 接受参数
res,将res赋值给reason保存 - 修改
status状态为失败rejected - 调用失败后的回调函数
onRejectedCallback
- 接受参数
then函数- 接受成功后的回调函数
onFulfilledCallback,失败后的回调函数onRejectedCallback - 若状态
status未完成,则保存对应的回调函数 - 若状态
status已完成,根据状态执行对应的回调函数
- 接受成功后的回调函数
- 字段
class MyPromise {
constructor(executor) {
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
executor(this.resolve, this.reject);
}
// 储存状态的变量,初始值是 pending
status = PENDING;
// 保存成功后的值
value = null;
// 保存失败的原因
reason = null;
// 存储成功回调函数
onFulfilledCallback = null;
// 存储失败回调函数
onRejectedCallback = null;
// 更改成功后的状态
resolve = (val) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = val;
// 判断成功回调是否存在,如果存在就调用
this.onFulfilledCallback && this.onFulfilledCallback(val);
}
};
// 更改失败后的状态
reject = (res) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = res;
// 判断失败回调是否存在,如果存在就调用
this.onRejectedCallback && this.onRejectedCallback(res);
}
};
then(onFulfilled, onRejected) {
// 判断状态
if (this.status === FULFILLED) {
// 调用成功回调,并且把值返回
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason);
} else if (this.status === PENDING) {
// 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
// 等到执行成功失败函数的时候再传递
this.onFulfilledCallback = onFulfilled;
this.onRejectedCallback = onRejected;
}
}
}
6. 实现一个简单的 Promise.all
原出处: 字节飞书面试——请实现promise.all - 掘金 (juejin.cn)
Promise.MyAll = function (promises) {
// 用于存储成功结果
const res = new Array(promises.length);
// 用于记录成功各数
let count = 0;
return new Promise((resolve, reject) => {
// 依次执行各promise
promises.forEach((item, index) => {
Promise.resolve(item).then((val) => {
// 成功后将结果存储到队列中
res[index] = val;
// 成功个数加一
count += 1;
// 当promises都成功时,返回成功的结果
if (count === promises.length) {
resolve(res);
}
}, reject);
});
});
};
7. 实现一个简单的 Promise.race
原出处: 字节飞书面试——请实现promise.all - 掘金 (juejin.cn)
// 和all类似,但是只需返回执行最快的结果即可
Promise.MyRace = function (promises) {
return new Promise((resolve, reject) => {
// 依次执行各promise
promises.forEach((item) => {
Promise.resolve(item).then((val) => {
resolve(val);
}, reject);
});
});
};
8. 实现一个简单的 Promise.any
原出处: 字节飞书面试——请实现promise.all - 掘金 (juejin.cn)
// 和all类似,但是只需返回执行最快的结果即可
Promise.MyAny = function (promises) {
const reason = new Array(promises.length);
let count = 0;
return new Promise((resolve, reject) => {
// 依次执行各promise
promises.forEach((item, index) => {
Promise.resolve(item).then(
(val) => {
// 只要成功一个,就将结果返回
resolve(val);
},
(res) => {
// 失败的原因保存
reason[index] = res;
// 失败个数加一
count++;
// 全部都失败时,返回失败队列
if (count === promises.length) {
reject(reason);
}
}
);
});
});
};
9. 实现一个简单的 Promise.allSettled
字节飞书面试——请实现promise.all - 掘金 (juejin.cn)
// 全部完成时将结果返回,没有rejected状态
Promise.MyAllSettled = function (promises) {
const res = new Array(promises.length);
let count = 0;
return new Promise((resolve) => {
// 依次执行各promise
promises.forEach((item, index) => {
Promise.resolve(item).then(
(val) => {
// 成功的结果保存
res[index] = { status: "fulfilled", value: val };
// 完成个数加一
count++;
// 全部都完成时,返回结果
if (count === promises.length) {
resolve(res);
}
},
(res) => {
// 失败的原因保存
res[index] = { status: "rejected", value: res };
// 完成个数加一
count++;
// 全部都完成时,返回结果
if (count === promises.length) {
resolve(res);
}
}
);
});
});
};
10. 实现一个动态并发池
type taskCallbacks = () => Promise<any>;
type ITask = { id: number; taskCallbacks: taskCallbacks };
/**动态并发池 */
export class PromisePoolDynamic<T> {
/**最大并发数量 */
private limit: number;
/**当前正在跑的数量 */
private runningCount: number;
/**等待队列 */
private queue: ITask[];
/**动态并发池 - 构造函数
* @param maxConcurrency 最大并发数量
*/
constructor(maxConcurrency: number) {
this.limit = maxConcurrency;
this.runningCount = 0;
this.queue = [];
}
taskOnWait(id) {
return this.queue.find((item) => item.id === id);
}
/** 添加任务 **/
addTask(args: ITask) {
const { id, taskCallbacks } = args;
if (this.taskOnWait(id)) {
return;
}
if (this.runningCount < this.limit) {
// 并发数量没满则运行
this.runTask({ id, taskCallbacks });
} else {
// 并发数量满则加入等待队列
this.queue.push({ id, taskCallbacks });
}
}
/** 运行任务 **/
private runTask(args: ITask) {
const { id, taskCallbacks } = args;
this.runningCount++; //当前并发数++
taskCallbacks()
.then((result) => {
this.runningCount--;
this.checkQueue();
})
.catch((error) => {
this.queue = this.queue.filter((item) => item.id !== id);
this.runningCount--;
this.checkQueue();
});
}
/** 运行完成后,检查队列,看看是否有在等待的,有就取出第一个来运行 **/
private checkQueue() {
if (this.queue.length > 0 && this.runningCount < this.limit) {
const nextTask = this.queue.shift()!;
this.runTask(nextTask);
}
}
}