一个关于Promise的问题
一道题带来的疑问
掘金上的一道题: JS 实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有两个,一个解法如下:
const timeout = (time) =>
new Promise((resolve) => {
setTimeout(resolve, time);
});
class Scheduler {
constructor() {
this.awaitArr = [];
this.count = 0;
}
async add(promiseCreator) {
if (this.count >= 2) {
await new Promise((resolve) => {
this.awaitArr.push(resolve);
});
}
this.count++;
const res = await promiseCreator();
this.count--;
if (this.awaitArr.length) {
this.awaitArr.shift()();
}
return res;
}
}
const scheduler = new Scheduler();
const addTask = (time, order) => {
scheduler.add(() => timeout(time)).then(() => console.log(order));
};
addTask(1000, "1000");
addTask(500, "500");
addTask(400, "400");
addTask(300, "300");
// 输出
// 500
// 400
// 1000
// 300
初看有些懵,this.awaitArr.push(resolve);
,好奇为什么不是吧promiseCreator
push 进去,仔细看下这个 Promise 里面的函数没有执行 resolve 函数,只是把 resolve 函数 push 进数组了。那么问题来了!
如果 Promise 的 resolve, reject 没有执行会怎么样?
执行以下代码
let tAsync = async function () {
console.log(`----->`)
await new Promise(() => {});
console.log(`<-----`)
};
tAsync();
//输出
----->
发现程序执行结束后并没有输出<-----
,
根据 Promise 定义MDN:
new Promise( function(resolve, reject) {...} /* executor */ );
executor 是带有 resolve 和 reject 两个参数的函数。 Promise 构造函数执行时立即调用 executor 函数,resolve 和 reject 两个函数作为参数传递给 executor(executor 函数在 Promise 构造函数返回所建 promise 实例对象前被调用)。resolve 和 reject 函数被调用时,分别将 promise 的状态改为 fulfilled(完成)或 rejected(失败)。如果在 executor 函数中抛出一个错误,那么该 promise 状态为 rejected。executor 函数的返回值被忽略。
看来由于 await 使异步功能的执行被暂停,直到 Promise 被解决(resolve 或 reject),并在实现后恢复执行异步功能。由于该 Promise 一直没有调用 resolve 和 reject 导致下面的程序无法执行。看到这又有个疑问?
在 Promise 的外部执行 resolve, reject 可以改变 Promise 的状态吗?
我们看到解法中的 resolve 在 Promise 外部被执行,这样是否可以改变 Promise 的状态呢? 将上面代码改造一下,验证看看:
let waitf = null;
let tAsync = async function () {
console.log(`----->`);
await new Promise((resolve) => {
waitf = resolve;
});
console.log(`<-----`);
};
tAsync();
waitf();
//输出
//----->
//<-----
结论
这时再回头看看上面的代码就容易理解了
- 当前执行并发大于 2 时,生成一个暂停的 Promise,把 resolve 添到一个数组中,下面的代码被暂停执行
- 当前执行并发不大于 2,立即执行异步操作并从数组中弹出最先 push 的 resolve 改变 Promise 的状态,
- 由于 Promise 被解决,最初被暂停的代码可以继续执行