Jas*_* Yu 0 javascript asynchronous node.js promise
我知道 async 不是并行的,但我现在遇到了一个非常有趣的情况。
async function magic(){
/* some processing here */
await async () => await prompt_for_user(); // 1st prompt
await async () => await prompt_for_user(); // 2nd prompt
}
magic(); // first
magic(); // second
magic(); // third
Run Code Online (Sandbox Code Playgroud)
从上面的程序中,我们可以很容易地预测出所有提示同时弹出。我尝试使用具有以下实现的队列来解决它:
const Queue = () => {
let promise;
return async (f) => {
while(promise) await promise;
promise = f();
const res = await promises;
promise = undefined;
return res;
};
};
const queue = Queue();
async function magic(){
/* some processing here */
await queue(async () => await prompt_for_user()); // 1st prompt
await queue(async () => await prompt_for_user()); // 2nd prompt
}
magic(); // first
magic(); // second
magic(); // third
Run Code Online (Sandbox Code Playgroud)
这会阻止提示一次全部弹出。但是还有第二个问题:
所以当第一个magic()被调用时。first.1向用户显示提示。程序继续,第二个magic()被调用。另一个提示second.1在出现之前等待第一个提示完成。然后程序继续,第三个magic()被调用并third.1再次等待first.1完成。当first.1完成后,即用户键入的值,second.1就会弹出第一,但我想first.2先弹出。
我知道一个明显的解决方案是一一等待魔法。但这会失去 js 给我们的异步优势。如果在提示前处理魔法重,则提示前需要一些时间。
主意?
由于在您发布信号量答案之前我无法理解您的总体目标,因此我将我试图回答的问题定义为这样。
这是一个方案,以您发布的方案为蓝本,但它使用一系列承诺(不旋转或轮询标志)来强制prompt()调用序列化(调用之间的任何内容.start(),.end()同时允许所有其他操作并行运行。这应该是CPU 使用效率更高。
let Semaphore = (function() {
// private data shared among all instances
let sharedPromise = Promise.resolve();
return class Sempaphore {
constructor() {
let priorP = sharedPromise;
let resolver;
// create our promise (to be resolved later)
let newP = new Promise(resolve => {
resolver = resolve;
});
// chain our position onto the sharedPromise to force serialization
// of semaphores based on when the constructor is called
sharedPromise = sharedPromise.then(() => {
return newP;
});
// allow caller to wait on prior promise for its turn in the chain
this.start = function() {
return priorP;
}
// finish our promise to enable next caller in the chain to get notified
this.end = function() {
resolver();
}
}
}
})();
// use random times to test our async better
function prompt(tag, n) {
log(tag, 'input please: ', n);
return new Promise((resolve) => {
setTimeout(resolve, Math.floor(Math.random() * 1000) + 500);
});
};
function log(...args) {
if (!log.start) {
log.start = Date.now();
}
let diff = ((Date.now() - log.start) / 1000).toFixed(3);
console.log(diff + ": ", ...args);
}
function randomDelay(low = 500, high = 1000) {
return new Promise((resolve) => {
setTimeout(resolve, Math.floor(Math.random() * (high - low)) + low);
});
}
async function magic1(tag){
// declare semaphore before any async code to reserve your order for semaphored code below
let s = new Semaphore();
// whatever sync or async code you want here
log(tag, 'do some busy async work 1a');
await randomDelay(800, 1200);
log(tag, 'do some busy work 1b');
// start of our serialized critical section
await s.start();
await prompt(tag, 1);
await prompt(tag, 2);
s.end();
// end of our serialized critical section
// whatever sync or async code you want here
log(tag, 'do more busy work 1c');
await randomDelay();
}
async function magic2(tag){
let s = new Semaphore();
log(tag, 'do some busy async work 2a');
// this delay purposely causes magic2 async delay to be shorter
// than magic1 for testing purposes
await randomDelay(100, 750);
log(tag, 'do some busy work 2b');
await s.start();
await prompt(tag, 3);
await prompt(tag, 4);
s.end();
log(tag, 'do more busy work 2c');
await randomDelay();
}
Promise.all([
magic1("magic1a"),
magic1("magic1b"),
magic2("magic2a"),
magic2("magic2b")
]).then(() => {
log("all done");
}).catch(err => {
log("err: ", err);
});Run Code Online (Sandbox Code Playgroud)
这是一些示例输出(由于出于测试目的而进行的随机异步延迟,输出会略有不同)。但是,输入调用将始终以完全相同的顺序:
0.000: magic1a do some busy async work 1a
0.003: magic1b do some busy async work 1a
0.004: magic2a do some busy async work 2a
0.004: magic2b do some busy async work 2a
0.600: magic2b do some busy work 2b
0.721: magic2a do some busy work 2b
0.829: magic1b do some busy work 1b
1.060: magic1a do some busy work 1b
1.061: magic1a input please: 1
2.179: magic1a input please: 2
2.860: magic1a do more busy work 1c
2.862: magic1b input please: 1
3.738: magic1b input please: 2
4.500: magic1b do more busy work 1c
4.502: magic2a input please: 3
5.845: magic2a input please: 4
6.497: magic2a do more busy work 2c
6.498: magic2b input please: 3
7.516: magic2b input please: 4
8.136: magic2b do more busy work 2c
9.097: all done
Run Code Online (Sandbox Code Playgroud)
一些解释:
你let s = new Sempaphore();在代码中的位置就是你这个函数“把自己放在行中”以进行序列化的地方,所以一些还没有把自己放在行中的东西让它的临界区被迫在这个函数的临界区之后。这“保留”了一个位置,但实际上还没有开始临界区。如果您有其他不确定的异步代码在临界区之前运行,这一点很重要。您需要在异步代码之前在此之前保留您的位置,但不要实际等待该位置直到临界区之前。
您await s.start();在函数中放置的位置就是您希望它实际等待您排队等待临界区的位置。
您放置的位置s.end()是关键部分的结尾(允许其他关键部分现在也可以在轮到它们时运行)。
此演示显示了在提示的关键部分之前和之后运行的异步代码。该代码可以与其他事物并行运行。
非输入相关的异步操作可以在输入提示之间交错,即使在同一个临界区(按设计)。只有输入提示被强制序列化。
| 归档时间: |
|
| 查看次数: |
4754 次 |
| 最近记录: |