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)
Related
I am using a chrome extension to activate infinite async instances of loops so they do not conflict with each other.
There is a list of values and one item is being passed to each individual loop. Those loops are executed in the content.js and are being managed by the background.js but they are initialized, started and cancelled from the popup.js.
Now big Question is how do I use best practices to make the management of multiple async loops as easy as possible?
Is there any possible way to also cancel these loops in an easy way?
example code:
content.js:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if(request.message.active){
console.log("do something");
dispatch(request.message);
}});
async function dispatch(alternator) {
if (alternator.active) {
await new Promise(resolve => setTimeout(resolve, alternator.timeout));
console.log("do something");
}
return;
}
This background.js should have a list of async loops to manage in an array or something easy to manage. The for-loop is consuming too much time and the timeout is causing too much load.
background.js
async function dispatchBackground() {
while (1) {
for (let i = 0; i < alternator.length; i++) {
if(alternator[i].active){
chrome.tabs.sendMessage(alternator[i].tab_id, {"message": alternator[i]});
}
}
await new Promise(resolve => setTimeout(resolve, 100));
}
return;
}
You should probably use a library.
...but that would be boring!
In the following 👇 code, macrotask uses requestIdleCallback to run a callback on a JS runtime macrotask.
The user supplies the data to process, the logic to run (synchronously) at each step, and the continuation condition; they do not have to worry about explicitly yielding to an API.
Function createTask constructs a generator to return steps until the continuationPredicate returns false. Generator functions enable us to suspend and resume synchronous code - which we need to do here to switch between tasks in a round-robin fashion. A more advanced solution could prioritise tasks according to a heuristic.
createCircularList returns a wrapper around an array that exposes add, remove, and next (get the next item in creation order or, if we are at the "end", loop around to the first item again).
createScheduler maintains the task list. While there are tasks remaining in the task list, this function will identify the next task, schedule its next step on a macrotask, and wait for that step to complete. If that was the final step in the current task, the task is then removed from the task list.
Note that the precise interleaving of the output of this code will depend on things like how busy your machine is. The intent of the demonstration is to show how the task queue can be added-to while it is being drained.
const log = console.log
const nop = () => void 0
const stepPerItem = (_, i, data) => i < data.length
const macrotask = (cb) => (...args) => new Promise((res) => (typeof requestIdleCallback ? requestIdleCallback : setTimeout)(() => res(cb(...args))))
const createTask = (data,
step,
continuePredicate = stepPerItem,
acc = null,
onDone = nop) =>
(function*(i = 0) {
while(continuePredicate(acc, i, data)) {
acc = step(acc, i, data)
yield [acc, onDone]
i++
}
return [acc, onDone]
})()
const createCircularList = (list = []) => {
const add = list.push.bind(list)
const remove = (t) => list.splice(list.indexOf(t), 1)
const nextIndex = (curr, currIndex = list.indexOf(curr)) =>
(currIndex === list.length - 1) ? 0 : currIndex + 1
const next = (curr) =>
list.length ? list[nextIndex(curr)] : null
return { add, remove, next }
}
const createScheduler = (tasks = createCircularList()) => {
let isRunning = false
const add = (...tasksToAdd) =>
(tasksToAdd.forEach((t) => tasks.add(t)),
!isRunning && (isRunning = true, go()))
const remove = tasks.remove.bind(tasks)
const go = async (t = null) => {
while(t = tasks.next(t))
await macrotask(({ done, value: [result, onDone] } = t.next()) =>
done && (tasks.remove(t), onDone(result)))()
isRunning = false
}
return { add, remove }
}
const scheduler = createScheduler()
const task1 = createTask([...Array(5)], (_, i) => log('task1', i))
const task2 = createTask([...Array(5)], (_, i) => log('task2', i))
const task3 = createTask([...Array(5)], (_, i) => log('task3', i))
scheduler.add(task1, task2)
setTimeout(() => scheduler.add(task3), 50) // you may need to fiddle with the `setTimeout` delay here to observe meaningful interleaving
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.
I'm building a guessing game with Node JS. After collecting some data on the back-end, I send it to the front-end and the game starts. The data contains all 10 levels, so the game can run on a single page. Each level runs for 10 seconds. After the time is up, the user selection is sent to the server, and a result comes back. The answer is displayed, and then the content is changed to the "next level" (using the content in the big data object, therefore no refresh is needed).
I'm having some issues with having 10 levels run for 10 seconds each (or ~12 seconds with a delay for displaying the results).
This can't be done in some type of loop, since all awaits for each level will run at once. For instance:
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
for (let i = 0; i < 10; i++) {
displayPage(i);
await timeout(10000);
const result = await $.post(...) // gets results
displayResults(result);
await timeout(2000);
}
all the timeouts will run at once, and it won't work.
I thought of using a setInterval, but I'm not sure how to.. since I want to wait 10 seconds until checking the input, and then display the results for 2 seconds, and then move on.
For now the result I came up with is:
displayPage(level1);
await timeout(10000);
const result = await $.post(...)
displayResults(result);
await timeout(2000);
displayPage(level2);
await timeout(10000);
const result = await $.post(...)
displayResults(result);
await timeout(2000);
displayPage(level3);
await timeout(10000);
const result = await $.post(...)
displayResults(result);
await timeout(2000);
displayPage(level4);
await timeout(10000);
const result = await $.post(...)
displayResults(result);
await timeout(2000);
...
This does not seem efficient and I think there's a better way to do this, but I'm not sure how.
Any suggestions would be appreciated. Thanks!
I think this is what you are looking for:
const pages = [1, 2, 3, 4, 5];
run();
async function run() {
for (let i = 0; i < pages.length; i++) {
await displayPage(i);
const result = 'Some result';
await displayResult(result);
}
}
function displayPage(number) {
text.innerText = 'Page ' + number;
return new Promise(res => {
setTimeout(res, 10000);
});
}
function displayResult(result) {
text.innerText = 'Result: ' + result;
return new Promise(res => {
setTimeout(res, 2000);
});
}
<div id="text"><div>
Another solution, without promises and loops:
const pages = [1, 2, 3, 4, 5];
let currentPageIndex = 0;
displayPage();
function displayPage() {
const index = currentPageIndex++;
if (pages[index] === undefined) return;
const pageNumber = pages[index];
text.innerText = 'Page ' + pageNumber;
const result = 'Some result';
setTimeout(() => {
displayResult(result);
}, 10000);
}
function displayResult(result) {
text.innerText = 'Result: ' + result;
setTimeout(() => {
displayPage();
}, 2000);
}
<div id="text"></div>
Your first option seems to work assuming it is wrapped within an async function:
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const testFunc = async () =>{
for (let i = 0; i < 10; i++) {
console.log('page' , i+1 , '- question')
await timeout(3000);
console.log('page' , i+1 , '- answer')
await timeout(1000);
}
}
testFunc()
use setInterval on about 1000ms to create worker and add a state-machine that toggles the playing(10s) and the waiting(2s). You need a procedure that does the post call to the server and an object to keep the data(levels and stuff).
for example:
setInterval(funcWorker,1000,s_gameObj);
function funcWorker(f_context){
var l_dateNow = new Date();
if(f_context.is_playing){
var l_elapsed = l_dateNow.getTime() - f_context.dateLevelStart.getTime();
if(l_elapsed.totalSeconds() >= f_context.LEVEL_TIME){
f_context.end_level();
}
}else if(f_context.is_waiting_user){
//same check as above but on the waiting start
....
f_context.next_level();
}else if(f_context.is_waiting_server){
//do whatever
}
}
the end_level() should set the state flag in the context(game) object, to sending and send to the server. As the server returns in the response function set the state to waiting user and init the corresponding time variable to now(). The next_level() should set the state to playing and init the corresponding time variable to now() so that the timer can count. Consider the code above as a reference and not as a copy-paste resource.
I wrote a Chip-8 emmulator in JavaScript (source) and made a playable browser version here.
It contains an HTML select:
<select>
<option value="PONG">PONG</option>
<option value="TETRIS">TETRIS</option>
</select>
Which loads a ROM from a file every time one is selected:
document.querySelector('select').addEventListener('change', event => {
const rom = event.target.value
loadRom(rom)
})
This loadRom function fetches the ROM, converts it to a useful form, and loads it into an instance of a CPU class. The cpu has a fetch-decode-execute cycle that gets called with step(). I created this cycle (main) function to call itself in a setTimeout.
const loadRom = async rom => {
const response = await fetch(`./roms/${rom}`)
const arrayBuffer = await response.arrayBuffer()
const uint8View = new Uint8Array(arrayBuffer);
const romBuffer = new RomBuffer(uint8View)
cpu.interface.clearDisplay()
cpu.load(romBuffer)
let timer = 0
async function cycle() {
timer++
if (timer % 5 === 0) {
cpu.tick()
timer = 0
}
await cpu.step()
setTimeout(cycle, 3)
}
cycle()
}
This works fine, until I load a new ROM with the select. Now the cycle is compounded, and the game goes twice as fast. Every time you load a new ROM, it compounds again and creates a new cycle.
How can I create an infinite loop, but stop it and start a brand new one without compounding it?
To start with, have the current timeout to be a persistent variable, and then call clearTimeout with it right before calling loadRom. If nothing has been loaded yet, the clearTimeout just won't do anything.
But because you have awaits as well, you'll need to check whether a new rom gets loaded while the awaits are going on. One way to accomplish this would be to have another persistent variable, the current romBuffer being used - if it's not the same as the romBuffer in the function closure, then another rom has started, so return immediately (and don't recursively create a timeout).
let timeout;
let currentRomBuffer;
const loadRom = async rom => {
const response = await fetch(`./roms/${rom}`)
const arrayBuffer = await response.arrayBuffer()
const uint8View = new Uint8Array(arrayBuffer);
const romBuffer = new RomBuffer(uint8View)
currentRomBuffer = romBuffer;
cpu.interface.clearDisplay()
cpu.load(romBuffer)
let timer = 0
async function cycle() {
timer++
if (timer % 5 === 0) {
cpu.tick()
timer = 0
}
await cpu.step();
if (romBuffer !== currentRomBuffer) {
return;
}
timeout = setTimeout(cycle, 3);
}
cycle()
};
Try using the setInerval and keep track of the handle.
Then clear it when loading the new (or the same) rom.
const loadRom = async rom => {
const response = await fetch(`./roms/${rom}`)
const arrayBuffer = await response.arrayBuffer()
const uint8View = new Uint8Array(arrayBuffer);
const romBuffer = new RomBuffer(uint8View)
cpu.interface.clearDisplay()
cpu.load(romBuffer)
// if rom is loaded, clear it!
if(this.lastLoad)
clearInterval(this.lastLoad);
let timer = 0
async function cycle() {
timer++
if (timer % 5 === 0) cpu.tick()
await cpu.step()
}
// keep track the handle.
this.lastLoad = setInterval(cycle, 3);
}
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.