Why javascript promise doesn't async in for loop? [duplicate] - javascript

This question already has answers here:
Correct way to write a non-blocking function in Node.js
(2 answers)
Closed 3 years ago.
I have code like this:
const loop1 = length => new Promise((resolve, reject) => {
try {
let b = 0
for (let i = 0; i < length; i++) b = b + i
resolve(b)
} catch (e) {
reject(e)
}
})
const loop2 = length => new Promise((resolve, reject) => {
try {
let b = 0
for (let i = 0; i < length; i++) b = b + i
resolve(b)
} catch (e) {
reject(e)
}
})
const startTime = new Date().getTime()
loop1(10000000000).then(result => {
const endTime = new Date().getTime()
const duration = endTime - startTime
console.log(`loop1: ${duration}`, result)
}).catch(error => console.log('loop1 error:', error))
loop2(1).then(result => {
const endTime = new Date().getTime()
const duration = endTime - startTime
console.log(`loop2: ${duration}`, result)
}).catch(error => console.log('loop2 error:', error))
const endTime = new Date().getTime()
const duration = endTime - startTime
console.log('duration', duration)
Why are the results like this?:
root#ububtu:~$ node .
duration 15539
loop1: 15545 49999999990067860000
loop2: 15545 0
Why isn't the result like this?:
root#ububtu:~$ node .
duration 0
loop2: 5 0
loop1: 15545 49999999990067860000
Why should wait loop1 for give the result?
Why not passed loop1 to give result loop2 first?
And why the duration time is not < 1 seconds but more than 15 seconds?

The function you pass into the Promise constructor (the promise executor function) is called synchronously. That's so that it can start the asynchronous process (whatever it is) that the promise represents.
So in your code, loop1's executor function runs synchronously, then the promise is returned, then loop2's executor function is run synchronously; later, the fulfillment handlers are called asynchronously.
Remember: Promises don't turn a synchronous thing into an asynchronous thing. They just provide a standardized means of observing the results of things that are already asynchronous.
If you update your code to model asynchronous operations (in this case I'll use setTimeout), you'll see loop2's handler called before loop1's, since it fulfills earlier:
const loop1 = length => new Promise((resolve) => {
setTimeout(resolve, length, length)
})
const loop2 = length => new Promise((resolve) => {
setTimeout(resolve, length, length)
})
const startTime = new Date().getTime()
loop1(800).then(result => {
const endTime = new Date().getTime()
const duration = endTime - startTime
console.log(`loop1: ${duration}`, result)
}).catch(error => console.log('loop1 error:', error))
loop2(1).then(result => {
const endTime = new Date().getTime()
const duration = endTime - startTime
console.log(`loop2: ${duration}`, result)
}).catch(error => console.log('loop2 error:', error))
const endTime = new Date().getTime()
const duration = endTime - startTime
console.log('duration', duration)

Related

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.

Promise delay implementation

I've tried to implement delays in Promises this way:
function postAndSetContentfulId (client, code) {
return delay().then(_ => {
return postToContentful(client, code).then(setCode)
})
}
function delay () {
let multipler = Math.floor((count++) / 100) + 1
let milliseconds = multipler * 2000
return new Promise(resolve => setTimeout(() => resolve(), milliseconds))
}
I need to delay an third party API call in order to avoid rate limiting issues. This seems not to be actually working as I get all executed in a bunch.
I've got a simple rate limited promise queue I wrote years ago, which has served me very well
const rateLimited = perSecond => {
const milliSeconds = Math.floor(1000 / perSecond);
let promise = Promise.resolve(Date.now());
return fn => promise.then(lastRun => {
const wait = Math.max(0, milliSeconds + lastRun - Date.now());
promise = promise.then(() => new Promise(resolve => setTimeout(resolve, wait))).then(() => Date.now());
return promise.then(() => fn());
});
};
const example = rateLimited(2); // 2 per second for the example
let allPromises = Array.from({length:10}, (_, i) => i)
.map(v => example(() => Promise.resolve(v)));
console.time('Elapsed');
console.log('Started - should take 5 seconds');
Promise.all(allPromises).then(results => {
console.log(results);
console.timeEnd('Elapsed');
});
For your code, you would use rateLimited like
const q = rateLimited(20); // 20 per second limit for example
function postAndSetContentfulId(client, code) {
return q(() => postToContentful(client, code).then(setCode));
}

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