Javascript nested asynchronous calls execution order - javascript

I'm having trouble understanding how asynchronous code runs in javascript.
I have a code similar to the following:
const start = name => console.log(`${name} started`);
const finish = name => console.log(`${name} finished`);
const wrap = async (promise, name) => {
start(name);
const promiseResult = await promise;
finish(name);
return promiseResult;
}
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const inner = async ms => {
return await sleep(1000);
}
const outer = async ms => {
await wrap(inner(ms), 'inner1');
await wrap(inner(ms), 'inner2');
await wrap(inner(ms), 'inner3');
}
const testPromise = async promise => {
const t0 = performance.now();
const promiseResult = await promise;
const t1 = performance.now();
console.log(`Running promise took ${t1 - t0} milliseconds`);
return promiseResult;
}
testPromise(wrap(outer(5000), 'outer'));
The output of the above code is:
inner1 started
outer started
inner1 finished
inner2 started
inner2 finished
inner3 started
inner3 finished
outer finished
Running promise took 3026.2199999997392 milliseconds
As you can see in the output, inner1 was started before outer started, which is very weird! What I expect is that all inner calls start and finish within the start and finish of outer.
I did a lot of research on Google but couldn't find anything helpful unfortunately.
What worked for me is to explicitly emulate wrap function for outer call like below:
const start = name => console.log(`${name} started`);
const finish = name => console.log(`${name} finished`);
const wrap = async (promise, name) => {
start(name);
const promiseResult = await promise;
finish(name);
return promiseResult;
}
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const inner = async ms => {
return await sleep(1000);
}
const outer = async ms => {
await wrap(inner(ms), 'inner1');
await wrap(inner(ms), 'inner2');
await wrap(inner(ms), 'inner3');
}
const testPromise = async () => {
const t0 = performance.now();
const outerName = 'outer'; // -- emulate calling `await wrap(outer(5000), 'outer')`
start(outerName); // --
const promiseResult = await outer(5000); // --
finish(outerName); // -- finished emulation of `wrap`
const t1 = performance.now();
console.log(`Running promise took ${t1 - t0} milliseconds`);
return promiseResult;
}
testPromise();
The output of the above code is what I really expect:
outer started
inner1 started
inner1 finished
inner2 started
inner2 finished
inner3 started
inner3 finished
outer finished
Running promise took 3155.5249999510124 milliseconds
What am I doing wrong that makes inner1 start before outer is started?

Your question demonstrates a number of misunderstandings about the effective use of async and await -
const sleep = ms =>
new Promise(r => setTimeout(r, ms))
async function wrap (p, label) {
console.log("started", label)
const t = Date.now()
const result = await p
console.log("finished", label)
return { result, delta: Date.now() - t }
}
async function inner () {
await sleep(1000)
return Math.floor(Math.random() * 100)
}
async function outer () {
const a = await wrap(inner(), "inner1")
const b = await wrap(inner(), "inner2")
const c = await wrap(inner(), "inner3")
return [a, b, c]
}
wrap(outer(), "outer")
.then(JSON.stringify)
.then(console.log, console.error)
started inner1
started outer
finished inner1
started inner2
finished inner2
started inner3
finished inner3
finished outer
{"result":[{"result":58,"delta":1004},{"result":58,"delta":1001},{"result":67,"delta":1000}],"delta":3009}
async and await are not special
It's a useful exercise to imagine that async and await do not exist and you have to invent them on your own -
const sleep = ms =>
new Promise(r => setTimeout(r, ms))
function* pick1 () {
yield Await(sleep(1000))
return Math.random()
}
function* pick3 () {
const a = yield Await(pick1())
console.log("first", a)
const b = yield Await(pick1())
console.log("second", b)
const c = yield Await(pick1())
console.log("third", c)
return [a, b, c]
}
Async(pick3()).then(console.log, console.error)
first 0.22559836642959197
second 0.41608184867397835
third 0.3789851899519072
[
0.22559836642959197,
0.41608184867397835,
0.3789851899519072
]
Note the uppercase Async and Await. These are plain functions of our own making -
const isGenerator = x =>
x?.constructor == (function*(){}()).constructor
const Await = x =>
isGenerator(x) ? Async(x) : Promise.resolve(x)
function Async (it) {
return new Promise((resolve, reject) => {
function next (x) {
const {value, done} = it.next(x)
return done
? resolve(value)
: value.then(next, reject)
}
next()
})
}
Hopefully this helps you see what's going on behind the scenes of async and await. It's nothing more than a bit of syntactic sugar to replace a program you could've written by yourself :D
Expand the snippet below to verify the behaviour of our homemade Async and Await below -
const isGenerator = x =>
x?.constructor == (function*(){}()).constructor
const Await = x =>
isGenerator(x) ? Async(x) : Promise.resolve(x)
function Async (it) {
return new Promise((resolve, reject) => {
function next (x) {
const {value, done} = it.next(x)
return done
? resolve(value)
: value.then(next, reject)
}
next()
})
}
const sleep = ms =>
new Promise(r => setTimeout(r, ms))
function* pick1 () {
yield Await(sleep(1000))
return Math.random()
}
function* pick3 () {
const a = yield Await(pick1())
console.log("first", a)
const b = yield Await(pick1())
console.log("second", b)
const c = yield Await(pick1())
console.log("third", c)
return [a, b, c]
}
Async(pick3()).then(console.log, console.error)
For more info on misuse of async and await, please see this related Q&A

After many attempts, I found that asynchronous functions run synchronously until they reach either await or return.
The behavior that is happening in the first code can be explained as the following:
As in any function call, function parameters are evaluated first and before evaluating the function itself, calling wrap(outer(5000), 'outer') will evaluate the outer(5000) function call first. While evaluating outer call, and especially the first line wrap(inner(ms), 'inner1'), the same thing will happen, inner(ms) will be evaluated first registering the first promise in these nested calls which will be wrapped by wrap function (which is the first call to wrap function), and since wrap(inner(ms), 'inner1') is the first asynchronous call (first await statement) in outer function, then it will be evaluated synchronously, after that all other await statements will register other promises that depend on the first promise await(inner(ms) 'inner1'), then the outer function will be wrapped by wrap. This is why inner1 is started first, outer is started after, then everything else runs as expected.
The solution I found for this is to pass a callback that returns the promise to wrap function instead of passing the promise right away.
const start = name => console.log(`${name} started`);
const finish = name => console.log(`${name} finished`);
const wrap = async (promiseCallback, name) => {
start(name);
const promiseResult = await promiseCallback();
finish(name);
return promiseResult;
}
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const inner = async ms => {
return await sleep(1000);
}
const outer = async ms => {
await wrap(() => inner(ms), 'inner1');
await wrap(() => inner(ms), 'inner2');
await wrap(() => inner(ms), 'inner3');
}
const testPromise = async promiseCallback => {
const t0 = performance.now();
const promiseResult = await promiseCallback();
const t1 = performance.now();
console.log(`Running promise took ${t1 - t0} milliseconds`);
return promiseResult;
}
testPromise(() => wrap(() => outer(5000), 'outer'));

well from my understanding of asynchronous JS, all the code runs at the same time as opposed to synchronous code.
The only reason why that could be happening is that inner1 execution happens faster than outer.

Related

Measuring await time in Node

Let's say I have a function in Node
const my_function = async () => {
await sub_function_1();
await sub_function_2();
await sub_function_3();
}
I could profile how long it takes my_function to run with:
let t0 = new Date()
await my_function()
let t1 = new Date()
let elapsed_ms = t1 - t0
But that wouldn't show me how long each of the sub_functions took to run. Similarly, I would add profiling code around each of the sub_functions, but that wouldn't show me which one of the sub-sub functions took the longest amount of time to run.
Is there a way, for a given function call, the gather all of the awaited functions underneath that call, along with how long each took to resolve?
what about a wrapper function which take a array of functions and runs them sequentially?
//simulating a lengthy request
const sub_function = (time) => {return new Promise((res,rej) => setTimeout( () => res(true),time ) )}
const new_list = [
{handler:sub_function(90), alias:"sub_function_1"},
{handler:sub_function(150), alias:"sub_function_2"},
{handler:sub_function(350), alias:"sub_function_3"},
]
const calculate_max_time = async list =>{
const time_passed = []
for (const func of list) {
const time = Date.now()
await func.handler
const time2 = Date.now()
time_passed.push({alias:func.alias, value:time2 - time})
}
const max_time = time_passed.reduce( (max,func) => max.value > func.value ? max : func)
console.log(max_time)
}
calculate_max_time(new_list)

Ordering promis by execution time

Hello is there any solution to order promises by execution time? for ex
Promise.all([Promise // 5ms ,Promise // 3ms,Promise // 2ms])
will return answer with same order as it was given is there any solution or method to sort it by execution time to have return like?
Promise.all([Promise // 2ms ,Promise // 3ms,Promise // 5ms])
To restate the problem clearly: given a set of promises, can they be executed concurrently, and can the results be arranged in an array in order of execution time?
If the execution time is known in advance, then sort the promises by execution time and pass the sorted array to Promise.all().
If the execution times are unknown in advance, I'd suggest wrapping the promises with a little function that times execution. Sort the result of Promise.all() on those elapsed times...
function sortedPromiseAll(array) {
const start = new Date()
const instrumentPromise = p => {
return p.then(result => {
const now = new Date();
return { result, duration: now.getTime()-start.getTime() }
});
}
const instrumentedPromises = array.map(instrumentPromise)
return Promise.all(instrumentedPromises).then(results => {
return results.sort((a, b) => a.duration-b.duration).map(r => r.result);
})
}
const timedPromise = t => {
return new Promise(resolve => {
setTimeout(resolve, t)
})
};
// imagine we don't know these timings
const promiseA = timedPromise(600).then(() => 'A');
const promiseB = timedPromise(300).then(() => 'B');
const promiseC = timedPromise(900).then(() => 'C');
// expected result is B, A, C
sortedPromiseAll([promiseA, promiseB, promiseC])
.then(result => console.log(result));
The easiest way is to push them to a new array as they come in. No need to create extra promises, just have to piggyback off the promise that is already created.
const getRandomPromise = () => {
return new Promise(resolve => {
var ms = Math.random() * 3000;
window.setTimeout(()=>resolve(ms), ms);
});
};
function orderPromises (promises) {
const orderedPromies = [];
const pushPromiseResults = promise =>
promise.then(result => (orderedPromies.push(result), result))
promises.forEach(pushPromiseResults);
return Promise.all(promises).then(()=>orderedPromies);
}
const myPromises = [getRandomPromise(), getRandomPromise(), getRandomPromise()];
orderPromises(myPromises).then(x => console.log(x));
You can get the ordering of when they resolve if you manually push the results of each promise into an array. In this example the value resolved is a string that shows the timestamp it was added.
Results are pushed into outputs array when they resolve. Printing outputs shows they are now ordered based on when they resolved.
const outputs = [];
function createPromise() {
return new Promise((resolve, reject) => {
// adding a bit of randomness here so there is no guarantee when they resolve
const delay = Math.random() * 1000;
setTimeout(() => {
resolve(`Promise resolved at ${Date.now()}`);
}, delay);
});
}
function addResult() {
return createPromise().then(value => {
outputs.push(value);
});
}
Promise.all([addResult(), addResult()]).then(() => console.log(outputs));

How to yield value multiple times from function?

So what I am doing is, I have 2 files, One that contain a script which would generate a token and the second file handle that token.
The problem is that the second script which would log the token it would only log the first token received.
This is the how I am handling the token:
const first_file = require("./first_file.js");
first_file.first_file().then((res) => {
console.log(res);
});
And clearly that wouldn't work, Because it's not getting updated with the newer value.
first_file = async () => {
return new Promise(async (resolve, reject) => {
//Generating the token
(async () => {
while (true) {
console.log("Resolving...");
resolve(token);
await sleep(5000);
resolved_token = token;
}
})();
});
};
module.exports = { first_file };
What I am doing here is, I tried to do a while..loop so that I keep resolving the token. But it didn't, Is there and way I can export the variable directly so the task would be easier ?
If I understand your question correctly, You want to resolve promise multiple times, And It's nothing to do with modules...
But You understand something wrong about promise in JavaScript...
You can't resolve a promise twice.
Generator
But you can generate new value from function, this type of function also known as generator, Where a function can reenter its context (Something like async/await) and yield result using yield keyword.
Usually a generator is used in for..of loop. It has next() method for yield next value from a generator...
Lets look an example:
const delay = ms => new Promise(res => setTimeout(res.bind(null, ms), ms));
async function* generator() {
yield 'yield result from generator!'
for (let ms = 100; ms <= 300; ms += 100) {
yield 'delay: ' + await delay(ms) + ' ms';
}
yield delay(1000).then(() => 'you can also yield promise!');
}
async function main() {
const gen = generator();
console.log('1st', (await gen.next()).value);
for await (const ms of gen) {
console.log(ms)
}
}
main()
Note that * after function, So that we know that this function a generator, with async keyword this is Async Generator.
Generator is very useful. like: Generate value on demand, Pass data like pipe!, Can return endless value from function etc...
Callback
This old school method heavily used in node, Where you pass a callback function as argument.
Example:
const delay = ms => new Promise(res => setTimeout(res.bind(null, ms), ms));
async function callback(fn) {
fn('yield result from callback!');
for (let ms = 100; ms <= 300; ms += 100) {
fn('delay: ' + await delay(ms) + ' ms');
}
await delay(1000);
fn('yield asynchronously!');
}
callback(value => console.log(value));
This approach create all sort of nutsy problem, like: created function scope, disaster of control flow, doesn't have break keyword etc...
I don't recommend this method.

Add a wait between each forEach statement

I want to add a wait time between each forEach statement in the below code, I don't want to run into problems with rate limits so I want to wait, say 1 second, between each time the forEach runs. Is this possible?
const Discord = require("discord.js");
const client = new Discord.Client();
const config = require("./config.json");
client.on("ready", () => {
console.log(`Bot has started, with ${client.users.size} users, in ${client.channels.size} channels of ${client.guilds.size} guilds.`);
});
client.on("message", async message => {
if(message.author.bot) return;
if(message.content.indexOf(config.prefix) !== 0) return;
const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
const command = args.shift().toLowerCase();
if(command === "addalltorole") {
process.setMaxListeners(n);
let role = message.guild.roles.find(r => r.name == 'TribeVerified')
if (!role) return message.channel.send(`**${message.author.username}**, role not found`)
var hasRoles = ["449032040147582977", "489252560532799488", "449032406444277760", "449032567988158465", "449032704122552320", "449032907332255759", "449033048374247426", "449033186413117441", "459119831183130645", "449033329946394645", "462285271505829909", "528059697257775106", "462061656852398090", "461635407893889037", "535632204026609665", "535632207222407168535637767242121216", "535637767242121216", "535637777388142593", "542049404270673942"];
message.guild.members.filter(member => member.roles.filter(r => hasRoles.includes(r.id)).size > 2).forEach(member => member.addRole(role));
message.channel.send(`**${message.author.username}**, role **${role.name}** was added to all applicable members`)
}
});
client.login(config.token);
I found this on stackoverflow elsewhere, not sure if it could be of use to implement what I'm trying to do?
var array = ['some', 'array', 'containing', 'words'];
var interval = 1000; // how much time should the delay between two iterations be (in milliseconds)?
array.forEach(function (el, index) {
setTimeout(function () {
console.log(el);
}, index * interval);
});
Thanks so much in advanced for any help!
Yes, that approach will work just fine.
It will space them 1 second apart regardless of how long they take to complete.
...and that's probably fine for what you are doing...
...but if you want you can use the fact that addRole returns a Promise to do some cool stuff.
For example, this uses reduce to pass along the Promises so that each call waits for the previous call to finish and then waits an additional second before making the next request:
message.guild.members.filter(member => member.roles.filter(r => hasRoles.includes(r.id)).size > 2)
.reduce(async (previous, member) => {
await previous; // wait for the previous call to finish
await new Promise(resolve => setTimeout(resolve, 1000)); // wait another second
return member.addRole(role); // pass this Promise to the next iteration
}, Promise.resolve()); // start with a resolved Promise
Here is a working demo you can run:
class Member {
constructor(name) { this.name = name; }
async addRole(role) {
await new Promise(resolve => setTimeout(resolve, 100)); // simulate asynchronous call
console.log(`added role "${role}" to ${this.name}`);
}
}
const members = [
new Member('first'),
new Member('second'),
new Member('third'),
new Member('fourth'),
new Member('fifth')
];
members.reduce(async (previous, member) => {
await previous;
await new Promise(resolve => setTimeout(resolve, 1000));
return member.addRole('admin');
}, Promise.resolve());
...but yes, either approach will work and should be just fine for what you are doing.

If I await 2 functions can I guarantee that the return object will have both values

I have the following code
module.exports = async function (req, res) {
const station1 = await getStation('one')
const station2 = await getStation('two')
return { stations: [station1, station2] }
}
Can I be guaranteed that when the final return value is sent it will definitely have both station1 and station2 data in them, or do I need to wrap the function call in a Promise.all()
As you have it, it is guaranteed that the return statement will only be executed when the two getStation() promises have resolved.
However, the second call to getStation will only happen when the first promise has resolved, making them run in serial. As there is no dependency between them, you could gain performance, if you would run them in parallel.
Although this can be achieved with Promise.all, you can achieve the same by first retrieving the two promises and only then performing the await on them:
module.exports = async function (req, res) {
const promise1 = getStation('one');
const promise2 = getStation('two');
return { stations: [await promise1, await promise2] }
}
Now both calls will be performed at the "same" time, and it will be just the return statement that will be pending for both promises to resolve. This is also illustrated in MDN's "simple example".
The await keyword actually makes you "wait" on the line of code, while running an async action.
That means that you don't proceed to the next line of code until the async action is resolved. This is good if your code has a dependency with the result.
Example:
const res1 = await doSomething();
if(res1.isValid)
{
console.log('do something with res1 result');
}
The following code example will await a promise that gets resolved after three seconds. Check the date prints to the console to understand what await does:
async function f1() {
console.log(new Date());
// Resolve after three seconds
var p = new Promise(resolve => {
setTimeout(() => resolve({}),3000);
});
await p;
console.log(new Date());
}
f1();
ES6Console
BTW, In your case, since you don't use the result of station1 it's better using Promise.all to work parallel.
Check this example (it will run for 3 seconds instead of 4 seconds the way you coded above):
async function f1() {
console.log(new Date());
// Resolve after three seconds
var p1 = new Promise(resolve => {
setTimeout(() => resolve({a:1}),3000);
});
// Resolve after one second
var p2 = new Promise(resolve => {
setTimeout(() => resolve({a:2}),1000);
});
// Run them parallel - Max(three seconds, one second) -> three seconds.
var res = await Promise.all([p1,p2]);
console.log(new Date());
console.log('result:' + res);
}
f1();
ES6Console.
If either of await getStation('one') or await getStation('two') fails an exception will be thrown from the async function. So you should always get the resolved value from both promises.
You can rewrite your function as follows to use Promise.all
module.exports = async function (req, res) {
try{
const [station1, station2] = await Promise.all([
getStation('one'),
getStation('two')
]);
return { stations: [station1, station2] };
} catch (e) {
throw e;
}
}

Categories

Resources