Strange behaviour with Promise.all - javascript

I have this code snippet that should await 5 promises, pause for 2 seconds and then continue to await another 5. However, the output shows that the two Promise.all are not awaited although when I tried to return a value in the promise it returns properly. Am I understanding the concept of Promise.all wrong or there is something missing in my code?
(function () {
let val = 0
const promiseArr = []
for (let i = 0; i < 10; i++) {
promiseArr[i] = new Promise((res) => {
val += 500
setTimeout((val2) => {
console.log(val2)
res()
}, val, val)
})
}
console.log();
(async function() {
await Promise.all(promiseArr.slice(0, 5))
console.log('start await')
await new Promise((res) => setTimeout(res, 2000))
console.log('done await')
await Promise.all(promiseArr.slice(6, 10))
})()
}) ()
Expected output:
500
...
2500
start await
done await
3000
...
5000
Actual output:
500
1000
1500
2000
2500
start await
3000
3500
4000
4500
done await
5000
EDIT:
Now I understand why it behaves like that. Looks like I misunderstood a fundamental concept with Promises. Thanks for you guys help!

You're seeing this because the promises begin running as soon as they are created (or, well, as soon as the current synchronous block finishes anyway), not when they are awaited upon.
Instrumenting and reorganizing your code a little, to print timestamps for each event might illuminate things:
const t0 = +new Date();
const logWithTime = (message = "") =>
console.log("" + Math.round(+new Date() - t0) + ": " + message);
(async function() {
let val = 0;
const resolver = res => {
val += 500;
const thisVal = val; // so we have a binding for the effective value
logWithTime("Creating timeout for " + thisVal);
setTimeout(
arg => {
logWithTime("Timeout for " + thisVal);
res();
},
thisVal,
thisVal,
);
};
const promiseArr = [];
for (let i = 0; i < 10; i++) {
promiseArr[i] = new Promise(resolver);
}
logWithTime("awaiting for first 5...");
await Promise.all(promiseArr.slice(0, 5));
logWithTime("waiting for 2 seconds...");
await new Promise(res => setTimeout(res, 2000));
logWithTime("waiting for the rest...");
await Promise.all(promiseArr.slice(6, 10));
logWithTime("all done");
})();
prints
0: Creating timeout for 500
10: Creating timeout for 1000
10: Creating timeout for 1500
10: Creating timeout for 2000
10: Creating timeout for 2500
10: Creating timeout for 3000
10: Creating timeout for 3500
10: Creating timeout for 4000
10: Creating timeout for 4500
10: Creating timeout for 5000
10: awaiting for first 5...
511: Timeout for 500
1011: Timeout for 1000
1515: Timeout for 1500
2013: Timeout for 2000
2510: Timeout for 2500
2511: waiting for 2 seconds...
3010: Timeout for 3000
3512: Timeout for 3500
4011: Timeout for 4000
4511: Timeout for 4500
4511: waiting for the rest...
5011: Timeout for 5000
5011: all done
(or thereabouts, depending on things).
You can see the order in which the timeouts get resolved there.

Your timers start ticking when you call setTimeout, not when you await the promise that is resolved by them.
While (res) => setTimeout(res, 2000) runs, you have 4 prior setTimeouts which finish and call console.log before resolving.

I stumbled on this misunderstanding myself too: when you define a Promise, it's function is executed in that moment. If I understand what you're trying to do, you need to define an array of functions, and call them as needed.
(function () {
let val = 0
const promiseArr = []
for (let i = 0; i < 10; i++) {
promiseArr[i] = () => new Promise((res) => {
val += 500
setTimeout((val2) => {
console.log(val2)
res()
}, val, val)
})
}
console.log();
(async function() {
await Promise.all(promiseArr.slice(0, 5).map((fn) => fn()))
console.log('start await')
await new Promise((res) => setTimeout(res, 2000))
console.log('done await')
await Promise.all(promiseArr.slice(6, 10).map((fn) => fn()))
})()
}) ()

Related

Make error throw in calling function instead of timeout

I have a loop which resets a timeout every time it completes an iteration, but if the loop takes too long I want to throw an error that is caught by an exception handler from the calling function. How can I throw the error in the parent instead of inside the timeout scope?
Where I'm applying this is attempting to access web pages and if not I have a set of instructions to reset the connection, but hopefully this mwe makes enough sense as a standalone example.
const wait = ms => new Promise(r => setTimeout(r, ms));
const getRand5 = () => Math.floor(Math.random() * 5 + 1);
const wait5 = () => wait(getRand5() * 1e2);
async function something() {
for (let i = 0; i < 10; i++) {
//I want this thrown error to throw an error for function something, not the timeout scope
const timer = setTimeout(() => { throw new Error("too long!"); }, 3e2);
await wait5();
clearTimeout(timer);
}
}
(async () => {
let fails = 0;
do {
try {
console.log("attempt number ", fails);
await something();
break;
} catch (e) {
fails++;
}
} while (fails < 10);
if (fails >= 10) {
console.log("failed too many times");
} else {
console.log("Looks like we gottem boys");
}
})();
You could solve this using Promise.race() with (of course) Promises.
Example:
// Timeout helper function
// Returns a promise which gets resolved after `ms` milliseconds
async function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms, "timed_out");
});
}
async function something() {
// Race Timeout and Wait5 promises here
// If Timeout promise resolves, it will
// resolve the value "timed_out"
const result = await Promise.race([
timeout(3e2),
wait5(),
]);
// Check resolved value here; if timed out, throw error
if (result === "timed_out") {
throw new Error("too long!");
}
}
From some comments this is how I solved the problem in my specific situation. Instead of trying to throw an error, I changed the function from async to returning a promise, and used the reject function to exit early if needed.
const wait = ms => new Promise(r => setTimeout(r, ms));
const getRand5 = () => Math.floor(Math.random() * 5 + 1);
const wait5 = () => wait(getRand5() * 1e2);
function something() {
return new Promise(async (resolve, reject) => {
for (let i = 0; i < 10; i++) {
//I want this thrown error to throw an error for function something, not the timeout scope
const timer = setTimeout(() => { reject() }, 4e2);
await wait5();
clearTimeout(timer);
}
resolve();
});
}
(async () => {
let fails = 0;
do {
try {
if (fails % 10 == 0)console.log("attempt number ", fails);
await something();
break;
} catch (e) {
fails++;
}
} while (fails < 100);
if (fails >= 100) {
console.log("failed too many times");
} else {
console.log("Looks like we gottem boys");
}
})();

What is the best way to handle function interrupts in javascript

Basically I have two async functions. One of them is a simple 5 second timeout and the other is a complicated async function with multiple steps.
Here is an example
const delay = ms => new Promise(res => setTimeout(res, ms));
class Runner {
async start() {
let printStuff = async () => {
for(let i = 0 ; i < 50; i++){
console.log(i);
await delay(50);
}
}
let printLetters = new Promise(async function(resolve, reject) {
const length = Math.floor(Math.random() * 10)
//dont know how long this will take
for(let i = 0; i < length; i++){
await printStuff();
}
resolve('letters');
});
let timeout = new Promise(async function(resolve, reject) {
await delay(5000);
resolve('timeout')
});
const finished = await Promise.all([timeout, printLetters]);
if(finished === 'timeout'){
this.stop();
}
}
stop(){
//stop printing stuff instantly
}
}
const myRunner = new Runner();
myRunner.start();
//could call myRunner.stop(); if the user canceled it
The way I would implement this would add a global variable and include an if statement inside the for loop to check if the interrupt has been called but I am wondering if there is a better way to implement this. An issue with this solution would be it would print a few more numbers. I would have to add another check to the other for loop and this could get messy quickly.
Here is a simple demo that uses my own library.
import { CPromise } from "c-promise2";
const task = CPromise.promisify(function* () {
let printStuff = CPromise.promisify(function* () {
for (let i = 0; i < 10; i++) {
console.log(i);
yield CPromise.delay(100);
}
});
const length = Math.floor(Math.random() * 10) + 3;
//dont know how long this will take
for (let i = 0; i < length; i++) {
yield printStuff();
}
return "letters";
});
const promise = task()
.timeout(5000)
.then(
(result) => console.log(`Done: ${result}`),
(err) => console.warn(`Fail: ${err}`)
);
setTimeout(() => {
promise.cancel();
}, 2000);
Is this what you're after?
What changed:
Promise.all replaced with Promise.race
added isStopped prop which makes the "complicated async function with multiple steps" skip execution for the remaining steps. it doesn't kill it, though. Promises are not cancelable.
const delay = ms => new Promise(res => setTimeout(res, ms));
class Runner {
isStopped = false;
async start() {
const printStuff = async () => {
let i = 0;
while (!this.isStopped) {
console.log(i++);
await delay(50);
}
}
const printLetters = new Promise(
resolve => printStuff()
.then(() => resolve('letters'))
);
const timeout = new Promise(
resolve => delay(5000)
.then(() => resolve('timeout'))
);
const finished = await Promise.race([timeout, printLetters]);
console.log({ finished });
if (finished === 'timeout') {
this.stop();
}
}
stop() {
this.isStopped = true;
}
}
const myRunner = new Runner();
myRunner.start();
<button onclick="myRunner.stop()">stop</button>
Initial answer (left it in as the comments reference it, not what's above; and in case someone finds it useful in 2074):
Here's an example outlining what I was suggesting in the comment. run() below returns a race between a rejector happening after 1s and a fulfiller which resolves in random time between 0 and 2s.
const rejector = (timeout) => new Promise((resolve, reject) => {
setTimeout(reject, timeout, 'rejected')
});
class Runner {
run() {
return Promise.race([
rejector(1000),
new Promise((resolve, reject) => {
setTimeout(resolve, Math.random() * 2000, 'fulfilled')
})
])
}
}
const t0 = performance.now();
[...Array(6).fill()].forEach((_, key) => {
const runner = new Runner();
runner.run()
.then(r => console.log(`Proomise ${key} ${r} after ${performance.now() - t0}ms`))
.catch(err => console.log(`Promise ${key} ${err} after ${performance.now() - t0}ms`));
})
Note: initially I placed the rejector inside the class but (at least for the above example) I don't see why it should not stay outside (in a real case scenario, imported from a helper file).
If you require an instantaneous stop capability, you would probably want to execute the print job as a external script. Then you use child processes like this.
const { spawn } = require('child_process');
class Runner {
......
start() {
this.job[somejobId] = spawn('command to execute script');
//this can be anything, including a node script, e.g. `node start.js`
.....
}
stop(jobId) {
if (jobId) {
//this will kill the script that you spawn above
this.job[jobId].kill('SIGHUP');
}
}
stopAllJobs() {
// looping through the job queue to kill all the jobs
this.job.forEach(job => job.kill('SIGHUP'))
}
}
You will have more info on how to start a child process from the node doc website https://nodejs.org/api/child_process.html#subprocesskillsignal
If your job (external script) is stalling, it's recommended that you only use the above codes if you have a minimum 2 CPU core, else it will affect your main process if your script is heavy.

Javascript concurrency and asynchronous operations

I am trying to understand how javascript handle concurrency with this script:
let buffer = [], total = [], count = 0;
const a = setInterval(() => {
buffer.push(count);
count++;
}, 1);
const b = setInterval(async () => {
await new Promise(resolve => setTimeout(resolve, 500));
total = [...total, ...buffer];
buffer = [];
}, 1);
setTimeout(() => {
clearInterval(a);
clearInterval(b);
console.log(total.length + " elements");
for (let i = 0; i < total.length; i++) {
if (i !== total[i]) {
console.log("Error on " + i);
}
}
}, 10000);
Example of output:
8227 tries
Done !
From what I understand, intervals A and B will run in a single thread, and while interval B is waiting for the promise to resolve, interval A will keep running.
However interval A will never be able to run after the promise is resolved and B's execution is resumed, so I shall never see an Error on x, is that correct ?
I tried to replace the interval like below and get errors, so I think I am correct, but I'd like to be 100% sure about this.
const b = setInterval(async () => {
total = [...total, ...buffer];
await new Promise(resolve => setTimeout(resolve, 500));
buffer = [];
}, 1);

How to measure the execution time of an asynchronous function in nodejs?

I'm trying to get the execution/response time of an asynchronous function that executes inside a node-fetch operation, like the following
async function getEditedData() {
var a = await fetch(`https://api.example.com/resorce_example`);
var b = await a.json();
// Some aditional treatment of the object obtained (b)
console.log("End of the asynchronous function")
}
I Used the library perf_hooks like this, but the execution time shows before
const hrtime = require ('perf_hooks').performance.now ;
var start = hrtime ();
getEditedData();
var end = hrtime ();
console.log (end - start);
I found the async_hooks library https://nodejs.org/api/perf_hooks.html#perf_hooks_measuring_the_duration_of_async_operations , but I canĀ“t understand how it works. I am a basic in javascript/nodejs
You could simply store Date.now() in some variable and then check Date.now() when your Promise resolves (or rejects) and subtract to find the difference. For example:
const simulateSomeAsyncFunction = new Promise((resolve, reject) => {
console.log('Initiating some async process, please wait...')
const startTime = Date.now();
setTimeout(() => {
resolve(Date.now() - startTime);
}, 3000);
});
simulateSomeAsyncFunction.then(msElapsed => {
console.log(`Async function took ${msElapsed / 1000} seconds to complete.`);
});
Note: You could write code that achieves the same thing and appears to be synchronous by using await/async since that is just "syntactic sugar" built on top of Promises. For example:
const simulateSomeAsyncFunction = () => {
console.log('Initiating some async process, please wait...');
return new Promise((resolve, reject) => {
setTimeout(resolve, 3000);
});
};
// Await is required to be called within an async function so we have to wrap the calling code in an async IIFE
(async() => {
const startTime = Date.now();
await simulateSomeAsyncFunction();
const msElapsed = Date.now() - startTime;
console.log(`Async function took ${msElapsed / 1000} seconds to complete.`);
})();
Starting with a simple async function -
const fakeAsync = async (value) => {
const delay = 2000 + Math.random() * 3000 // 2 - 5 seconds
return new Promise(r => setTimeout(r, delay, value))
}
fakeAsync("foo").then(console.log)
console.log("please wait...")
// "please wait..."
// "foo"
We could write a generic function, timeit. This is a higher-order function that accepts a function as input and returns a new function as output. The new function operates like a decorated version of the original -
const timeit = (func = identity) => async (...args) => {
const t = Date.now()
const result = await func(...args)
return { duration: Date.now() - t, result }
}
// decorate the original
const timedFakeAsync = timeit(fakeAsync)
// call the decorated function
timedFakeAsync("hello").then(console.log)
timedFakeAsync("earth").then(console.log)
// { duration: 3614, result: "earth" }
// { duration: 4757, result: "hello" }
The timed version of our function returns an object, { duration, result }, that reports the runtime of our async function and the result.
Expand the snippet below to verify the results in your own browser -
const identity = x =>
x
const timeit = (func = identity) => async (...args) => {
const t = Date.now()
const result = await func(...args)
return { duration: Date.now() - t, result }
}
const fakeAsync = async (value) => {
const delay = 2000 + Math.random() * 3000 // 2 - 5 seconds
return new Promise(r => setTimeout(r, delay, value))
}
const timedFakeAsync = timeit(fakeAsync)
timedFakeAsync("hello").then(console.log)
timedFakeAsync("earth").then(console.log)
console.log("please wait...")
// "please wait..."
// { duration: 3614, result: "earth" }
// { duration: 4757, result: "hello" }
If you expect to set the end after getEditedData() is completed, you actually need to await getEditedData(). Otherwise, you'll go right past it while it executes... asynchrnously.

Sleep in Node.js

Assumed that there is no "native" way to achieve this, my solution-like was
sleep = function(time) {
var stop = new Date().getTime();
while(new Date().getTime() < stop + time) {
;
}
return new Promise((r,_)=> r())
}
So doing sleep(1000*3).then(()=>console.log("awake")) it will sleep 3 seconds and then resolve the Promise:
(be aware that it will freeze this page one sec.)
sleep = function(time) {
var stop = new Date().getTime();
while (new Date().getTime() < stop + time) {;
}
return new Promise((r, _) => r())
}
console.log("sleeping...")
sleep(1000 * 1).then(() => console.log("awake"))
Assumed that this will run in the main thread it will freeze the main process so that doing
sleep(1000*1).then(()=>console.log("awake")); console.log("Hello")
it will result in a output
VM2628:1 Hello
VM2628:1 awake
at very end of the sleep. Of course doing
setTimeout(()=>sleep(1000*3).then(()=>console.log("awake")),1000);console.log("Hello")
VM2815:1 Hello
undefined
VM2815:1 awake
will make it async, but it does not address my need (to put to sleep my main process).
Any better way?
[UPDATE]
Promisified version
/**
* Sleep for time [msec]
* #param time int milliseconds
* #return Promise delayed resolve
* #usage
sleep(1000*3).then(()=>console.log("awake"))
*/
sleepP: function (time) {
return new Promise((resolve, reject) => {
var stop = new Date().getTime();
while (new Date().getTime() < stop + time) {
;
}
return resolve(true)
});
}
that can be called like
await sleepP( 1000 * 3 );
There is no need to freeze at all. Because of javascripts asynchronicity we can leave a part of the code for some time and resume later. At first we need a promising timer:
const timer = ms => new Promise( res => setTimeout(res, ms));
Then we can simply use it:
console.log("wait 3 seconds")
timer(3000).then(_=>console.log("done"));
Or with a bit syntactic sugar:
(async function(){
console.log("wait 3 seconds");
await timer(3000);
console.log("done");
})()
If you really want to freeze ( very bad ), you don't need promises at all:
function freeze(time) {
const stop = new Date().getTime() + time;
while(new Date().getTime() < stop);
}
console.log("freeze 3s");
freeze(3000);
console.log("done");
Since Node v15.0.0 there's a new way to sleep by using the Timers Promises API.
import {setTimeout} from 'timers/promises';
const bucket = ['a', 'b', 'c'];
for(const item of bucket) {
await getItem(item);
await setTimeout(100);
}
If you want to use also use the regular setTimeout timer you can alias the promise timer.
import {setTimeout as sleep} from 'timers/promises';
// promise `setTimeout` aliased as `sleep`
sleep(100).then(() => console.log('I waited'));
// regular callback `setTimeout` (scheduling timers)
setTimeout(() => console.log('I also waited'), 100);
function sleep(time, func){
if (typeof func === 'function'){
const timer = ms => new Promise( res => setTimeout(res, ms));
timer(time).then(i=>func());
}
else{
console.log('What about the function bro?')
}
}
sleep(1000, function(){
console.log('hello')
console.log('test')
var arr = [1,2,3,4]
arr.forEach(i => console.log(i))
})

Categories