There's something about chaining promises that I don't understand. The Node.js snippet below produces the following output. Why is promise.allSettled called after the first sleep on line 18 and not after the 2nd one on line 21?
Cycle 0 is going to sleep promise.js:2
Cycle 0 slept for 2 seconds promise.js:6
Returned from sleep function promise.js:19
Cycle 0 is going to sleep promise.js:2
Done with the process promise.js:27
Cycle 0 slept for 2 seconds promise.js:6
function sleep(cycle) {
console.log(`Cycle ${ cycle } is going to sleep`);
return new Promise(resolve => {
setTimeout(() => {
console.log(`Cycle ${ cycle } slept for 2 seconds`);
resolve();
}, 2000);
});
}
function process() {
let cycles = 1;
let subprocesses = [];
for (let i = 0; i < cycles; i++) {
subprocesses.push(
sleep(i).then(() => {
console.log('Returned from sleep function');
sleep(i);
})
);
}
Promise.allSettled(subprocesses).then(results => {
console.log('Done with the process');
});
}
process();
Because you haven't resolved the promise created sleep(i).then to the promise from the second sleep, so it doesn't wait for that second operation to finish before settling. You need a return in front of the second call:
function sleep(cycle) {
console.log(`Cycle ${ cycle } is going to sleep`);
return new Promise(resolve => {
setTimeout(() => {
console.log(`Cycle ${ cycle } slept for 2 seconds`);
resolve();
}, 2000);
});
}
function process() {
let cycles = 1;
let subprocesses = [];
for (let i = 0; i < cycles; i++) {
subprocesses.push(
sleep(i).then(() => {
console.log('Returned from sleep function');
return sleep(i); // <============================
})
);
}
Promise.allSettled(subprocesses).then(results => {
console.log('Done with the process');
});
}
process();
Here's a version with two cycles, and with the sleep calls more clearly marked:
function sleep(cycle, sub) {
console.log(`Cycle ${cycle}(${sub}) is going to sleep`);
return new Promise(resolve => {
setTimeout(() => {
console.log(`Cycle ${cycle}(${sub}) slept for 2 seconds`);
resolve();
}, 2000);
});
}
function process() {
let cycles = 2;
let subprocesses = [];
for (let i = 0; i < cycles; i++) {
subprocesses.push(
sleep(i, "a").then(() => {
console.log(`Returned from sleep function (${i})`);
return sleep(i, "b"); // <============================
})
);
}
Promise.allSettled(subprocesses).then(results => {
console.log('Done with the process');
});
}
process();
It might be clearer to split that sleep, sleep serial operation off into its own function, perhaps an async function:
async function twoSleep(i) {
await sleep(i, "a");
console.log(`Returned from sleep function (${i})`);
await sleep(i, "b");
}
function process() {
let cycles = 2;
let subprocesses = [];
for (let i = 0; i < cycles; i++) {
subprocesses.push(twoSleep(i));
}
Promise.allSettled(subprocesses).then(results => {
console.log('Done with the process');
});
}
function sleep(cycle, sub) {
console.log(`Cycle ${cycle}(${sub}) is going to sleep`);
return new Promise(resolve => {
setTimeout(() => {
console.log(`Cycle ${cycle}(${sub}) slept for 2 seconds`);
resolve();
}, 2000);
});
}
async function twoSleep(i) {
await sleep(i, "a");
console.log(`Returned from sleep function (${i})`);
await sleep(i, "b");
}
function process() {
let cycles = 2;
let subprocesses = [];
for (let i = 0; i < cycles; i++) {
subprocesses.push(twoSleep(i));
}
Promise.allSettled(subprocesses).then(results => {
console.log('Done with the process');
});
}
process();
The issue is that Promise.allSettled runs all promises in parallel, so all of them resolve at the same time. If you want to run them one by one try
async function process() {
const cycles = 1;
for (let i = 0; i < cycles; i++) {
await sleep(i).then(() => {
console.log("Returned from sleep function");
return sleep(i);
});
}
console.log("Done with the process");
}
process();
Related
I have a function that I need to run multiple times but wait until the previous one has finished.
Currently What I have is:
for(var x = 0; x < rollsArr.length; x++){
rollDice(data.results, x)
}
function rollSkins(results, rollN) {
// do some stuff
setTimeout(function() {
roll(results, rollN)
}, 500);
}
function roll(results, rollN) {
// do some stuff
setTimeout(function() {
// do some stuff
}, 8500);
}
I would like a roll function to finish then wait a few seconds and then start another roll. I tried awaiting rollDice but I didn't implement it correctly or it did not work.
What would be the solution for this?
You can use async/await or Promise.then(). Reference: https://zellwk.com/blog/async-await-in-loops/
const rollsArr = [1,2,3];
const data = {results: "example"};
const sleep = ms => {
return new Promise(resolve => setTimeout(resolve, ms))
}
(async () => {
for (var x = 0; x < rollsArr.length; x++){
await rollDice(data.results, x)
}
})()
async function rollDice(results, rollN) {
// do some stuff
await sleep(500);
await roll(results, rollN);
}
async function roll(results, rollN) {
// do some stuff
console.log("Rolling dice");
await sleep(1000);
console.log("Doing stuff");
}
I have the following code and I have tried everything, but I can't find the solution, I hope someone can help me.
So far the code executes the fRoll.roll, enters the FOR loop and executes the countdown of the EventEmitter, at the end it executes the fRoll.roll again, but the For loop is executed without respecting the time of the setInterval, which corresponds to the startTimer () of the EventEmitter.
My question is how can I make between each iteration of the loop wait for the setInterval of the EventEmitter and then execute the listeners.
const appGlobal = async (page, website) => {
const { EventEmitter } = require('events');
try {
class CountDownRoll extends EventEmitter {
constructor(CountDownRollTime) {
super();
this.CountDownRollTime = CountDownRollTime;
this.currentTime = 0;
}
startTimer() {
const timer = setInterval(() => {
this.currentTime++;
this.emit('update', this.currentTime);
// Check if CountDownRoll has reached to the end
if (this.currentTime === this.CountDownRollTime) {
clearInterval(timer);
this.emit('roll');
}
// Check if CountDownRoll will end in 10 minutes
if (this.currentTime === this.CountDownRollTime - 600) {
this.emit('end-soon');
}
}, 1000);
}
}
//======================================================
// this code is executed
const fRoll = require('../task/roll')(page, website)
fRoll.roll
//======================================================
// I need this loop to repeat itself X number of times and in each iteration,
// wait for the countdown before executing the codes.
const count = 10
for (let start = 1; start < count; start++)
const myCountDownRoll = new CountDownRoll(3600);
// Countdown
myCountDownRoll.on('update', (timer) => {
console.log(`${timer} seconds has been passed since the timer started`);
// code that performs at the end of the countdown
myCountDownRoll.on('roll', () => {
console.log('✓ CountDownRoll is completed');
//======================================================
// this code is executed
const fRoll = require('../task/roll')(page, website)
fRoll.roll
//======================================================
});
// Code that performs when reaching a specific time of the countdown
myCountDownRoll.on('end-soon', () => {
console.log('✓ Count down will be end in 600 seconds');
// Code to execute here
});
myCountDownRoll.startTimer();
}
} catch (e) {
console.log('Error intro app-global:', e)
}
}
module.exports = appGlobal
What happens to me is that it is executed 10 times for each time the listener of the countdown event is executed.
I have redone the code for a sleep function, as I have not found a way to use thestartTimer ()of the EventEmitter as a condition for each iteration of the loop.
const appGlobal = async (page, website) => {
try {
async function sleep(sec) {
return new Promise((resolve) => {
setTimeout(function () {
resolve()
}, sec * 1000)
})
}
async function roll() {
let count = 10000
for (let start = 1; start < count; start++) {
// We call the roll
const freeRoll = require('../task/roll')(page, website)
freeRoll.roll
let random = Math.round(Math.random() * 8)
console.log("Number is:" + " " + random)
if (random === 3) {
// We call the wine
const fwine = require('../task/wine')(page, website)
fwine.wine
await sleep(3600)
} else {
await sleep(3600)
}
}
}
roll();
} catch (e) {
console.log('Error intro app-global:', e)
}
}
module.exports = appGlobal
I have a function say myMainFunction that is called from a client, that in turn calls mypromisified function.
Scenario:
mypromisified function can fail intermittently and I need to call this function with a delay (at an exponential increase) until success or until max no of tries reached.
What I have so far
The following code illustrates my scenario and repeats itself until success, but it tries indefinitely and not until certain count is reached
// called once from the client
myMainFuntion();
function rejectDelay(delay, reason) {
// call main function at a delayed interval until success
// but would want to call this only a limited no of times
setTimeout(() => {
myMainFuntion(); // calling main function again here but with a delay
}, delay);
}
function myMainFuntion() {
var delay = 100;
var tries = 3;
tryAsync().catch(rejectDelay.bind(null, delay));
}
function tryAsync() {
return new Promise(function(resolve, reject) {
var rand = Math.random();
console.log(rand);
if (rand < 0.8) {
reject(rand);
} else {
resolve();
}
});
}
while loop inside the rejectDelay would certainly not work as the counter would increment even before the actual function inside setInterval is executed, so am unsure as to how to go about this? so...
I tried promisifying the setInterval something like this knowing it will fail :( as it doesnt decrement the counter, but not sure how to get it right either .
function rejectDelay(delay, maximumTries, reason) {
return new Promise(function (resolve, reject) {
console.log(tries + ' remaining');
if (--maximumTries > 0) {
setTimeout(function() {
foo();
}, 500);
}
});
}
function myMainFunction() {
var delay = 100;
var maximumTries = 3;
tryAsync().catch(rejectDelay.bind(null, delay, maximumTries));
}
Using a couple of helper functions I've used a lot, this becomes very easy
The "helpers"
Promise.wait = (time) => new Promise(resolve => setTimeout(resolve, time || 0));
Promise.retry = (cont, fn, delay) => fn().catch(err => cont > 0 ? Promise.wait(delay).then(() => Promise.retry(cont - 1, fn, delay)) : Promise.reject('failed'));
The code:
function myMainFuntion() {
var delay = 100;
var tries = 3;
Promise.retry(tries, tryAsync, delay);
}
ES5 versions of the helpers
Promise.wait = function (time) {
return new Promise(function (resolve) {
return setTimeout(resolve, time || 0);
});
};
Promise.retry = function (cont, fn, delay) {
return fn().catch(function (err) {
return cont > 0 ? Promise.wait(delay).then(function () {
return Promise.retry(cont - 1, fn, delay);
}) : Promise.reject('failed');
});
};
A slightly different approach that uses "asynchronous recursion" to call a nested function within a function that returns a promise:
function retry( func, maxTries, delay) {
var reTry = 0;
return new Promise( function(resolve, reject) {
function callFunc() {
try
{
func().then(resolve, function( reason) {
if( ++reTry >= maxTries) {
reject( reason);
}
else {
setTimeout( callFunc,
typeof delay=="function" ? delay( retry) : delay );
}
});
}
catch(e) {
reject(e);
}
}
callFunc();
});
}
// ******* run snippet to test ********
var retryCount = 0;
function getDelay( n) {
// return 100 * n*n + 500; // for example
++ retryCount;
return 100; // for testing
}
function testFunc() {
return Math.random() < 0.8 ? Promise.reject("too many tries") : Promise.resolve( "success");
}
retry( testFunc, 5, getDelay).then(
function(data) { console.log("data: %s, retryCount %s", data, retryCount);},
function(reason){console.log("reason: %s, retryCount %s", reason, retryCount);}
)
I have a promise-returning function that does some async stuff, let's call it functionToRepeat().
I am trying to write the function repeatFunction(amount) , so that it will start the promise, wait for completion, start it again, wait for completion, and so on a given amount of times. This repeatFunction(amount) should also be thenable, so that I can chain other stuff after it's been executed.
Here is my attempt:
function functionToRepeat(){
let action = new Promise(function(resolve,reject){
setTimeout(function(){
console.log("resolved!");
resolve()}
,1000);
})
return action
}
function repeatFunction(amount) {
if(amount==0){
return Promise.resolve();
}
return functionToRepeat().then(function(){
repeatFunction(amount-1);
});
}
repeatFunction(5).then(function(){
console.log("DONE!");
})
This successfully chains my promises (or so it seams, I get one "resolved!" per second in the console).
However the .then() I try to chain after my repeatFunction(5) happens after the 1st promise ends, not after all 5 have ended!
So in my console I get:
resolved!
DONE!
resolved!
resolved!
resolved!
resolved!
What am I doing wrong and what should I change?
How about simply:
function repeat(func, times) {
var promise = Promise.resolve();
while (times-- > 0) promise = promise.then(func);
return promise;
}
When tested with this:
function oneSecond() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log("tick");
resolve();
}, 1000);
});
}
repeat(oneSecond, 5).then(function () {
console.log("done");
});
this output is produced over 5 seconds:
tick
tick
tick
tick
tick
done
I think you are almost there, but you have to return the repeatFunction again in the then block of your function to repeat.
return functionToRepeat().then(function(){
return repeatFunction(amount-1);
});
}
If you have a then, but do not return anything, then it will just resolve the upper promise. That is what happened.
You're missing a return when you call repeatFunction(amount-1)
function functionToRepeat(){
let action = new Promise(function(resolve,reject){
setTimeout(function(){
console.log("resolved!");
resolve()}
,1000);
})
return action
}
function repeatFunction(amount) {
if(amount==0){
return Promise.resolve();
}
return functionToRepeat().then(function(){
return repeatFunction(amount-1); // Added return
});
}
repeatFunction(5).then(function(){
console.log("DONE!");
})
https://plnkr.co/edit/93T6B4QkBv0mYS4xPw0a?p=preview
You could use async/await with a simple while loop. This keeps you in Asyncland allows you to continue chaining after the repeated function is done
async function asyncRepeat (f, n) {
while (n-- > 0)
await f()
}
asyncRepeat(functionToRepeat, 5).then(() => {
console.log('done')
})
// some value
// some value
// some value
// some value
// some value
// done
This is trash tho. You're restricted to using a side-effecting function as your f argument to asyncRepeat. So logging to the console works, but what if you actually wanted to do something with that value?
Here's an update to asyncRepeat that allows you to thread a value thru the repeated application of your input function (asyncDouble in this example)
(important changes in bold)
async function asyncRepeat (f, n, x) {
while (n-- > 0)
x = await f(x)
return x
}
function asyncDouble (x) {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('x is currently: %d', x)
resolve(x * 2) // resolve an actual value
}, 1000)
})
}
asyncRepeat(asyncDouble, 5, 2).then(result => {
console.log('result: %d', result)
})
// x is currently: 2
// x is currently: 4
// x is currently: 8
// x is currently: 16
// x is currently: 32
// result: 64
I wanted something similar, so I wrote a generic function at (https://repl.it/#turlockmike/BriskLovableLinuxkernel)
function repeat(fn, times) {
if (times == 1) {
return fn()
} else {
return new Promise(function(resolve, reject) {
return fn().then(function() {
return resolve(repeat(fn,times - 1))
})
})
}
}
Usage
function doSomething() {
return new Promise(function(resolve, reject) {
//do something interested here
setTimeout(function(){
console.log("resolved!");
resolve()}
,1000);
})
}
repeat(doSomething, 5).then(() => {
console.log("all Done!")
})
const http = require('http');
const https = require('https');
const { t, d, r } = require('minimist')(process.argv.slice(2));
const checkRoot = config => {
const { delaySeconds, rootUrl } = config ? config : { delaySeconds: 6 };
const delay = delaySeconds * 1000;
const protocolString = rootUrl.split(':')[0];
const protocol = {
http: http,
https: https,
};
return new Promise(res => {
setTimeout(() => {
protocol[protocolString]
.get(rootUrl, resp => {
let data = '';
resp.on('data', chunk => {
data += chunk;
});
resp.on('end', () => {
res({ success: data.includes('<!doctype html>') });
});
})
.on('error', err => {
console.log(`Error: ${err.message}`);
res({ success: false });
});
}, delay);
});
};
const repeatChecking = async ({ times, delaySeconds, rootUrl }) => {
let isReady = false;
console.log(
`will try ${times}, and with ${delaySeconds} seconds delay in between for ${rootUrl}`
);
let i = 1;
while (i <= times) {
if (isReady === true) {
break;
}
const res = await checkRoot({ delaySeconds, rootUrl }).then();
isReady = res.success;
console.log(`testing ${i}, status: root ready => ${res.success}`);
i++;
}
if (isReady) {
console.log('Done, root is ready');
return;
}
process.stdout.write('ERROR: root could not be reached\n');
process.exit(1);
};
repeatChecking({ times: t, delaySeconds: d, rootUrl: r });
let loopP = (n, f, ...args) => {
let p = f(...args);
p.then(res => {
if (n - 1) {
loopP(n - 1, f, res);
}
});
};
Where n is the number of iterations, f is the Promise-returning function to be called. Each successive call to f is passed the results of the previous call when it resolves.
For instance...
let addOneP = i => {
console.log(i + 1);
return Promise.resolve(i + 1);
};
loopP(5, addOneP, 0);
// logs:
// 1
// 2
// 3
// 4
// 5
You might find relign useful for this kind of thing. Here's your example written with relign series and relign setTimeout.
const fnToRepeat = () =>
relign.setTimeout(() => console.log("resolved!"), 1000);
relign.series((new Array(5)).fill(fnToRepeat))
.then(() => console.log('done'));
Is it possible to chain setTimout functions to ensure they run after one another?
Three separate approaches listed here:
Manually nest setTimeout() callbacks.
Use a chainable timer object.
Wrap setTimeout() in a promise and chain promises.
Manually Nest setTimeout callbacks
Of course. When the first one fires, just set the next one.
setTimeout(function() {
// do something
setTimeout(function() {
// do second thing
}, 1000);
}, 1000);
Chainable Timer Object
You can also make yourself a little utility object that will let you literally chain things which would let you chain calls like this:
delay(fn1, 400).delay(fn2, 500).delay(fn3, 800);
function delay(fn, t) {
// private instance variables
var queue = [], self, timer;
function schedule(fn, t) {
timer = setTimeout(function() {
timer = null;
fn();
if (queue.length) {
var item = queue.shift();
schedule(item.fn, item.t);
}
}, t);
}
self = {
delay: function(fn, t) {
// if already queuing things or running a timer,
// then just add to the queue
if (queue.length || timer) {
queue.push({fn: fn, t: t});
} else {
// no queue or timer yet, so schedule the timer
schedule(fn, t);
}
return self;
},
cancel: function() {
clearTimeout(timer);
queue = [];
return self;
}
};
return self.delay(fn, t);
}
function log(args) {
var str = "";
for (var i = 0; i < arguments.length; i++) {
if (typeof arguments[i] === "object") {
str += JSON.stringify(arguments[i]);
} else {
str += arguments[i];
}
}
var div = document.createElement("div");
div.innerHTML = str;
var target = log.id ? document.getElementById(log.id) : document.body;
target.appendChild(div);
}
function log1() {
log("Message 1");
}
function log2() {
log("Message 2");
}
function log3() {
log("Message 3");
}
var d = delay(log1, 500)
.delay(log2, 700)
.delay(log3, 600)
Wrap setTimeout in a Promise and Chain Promises
Or, since it's now the age of promises in ES6+, here's similar code using promises where we let the promise infrastructure do the queuing and sequencing for us. You can end up with a usage like this:
Promise.delay(fn1, 500).delay(fn2, 700).delay(fn3, 600);
Here's the code behind that:
// utility function for returning a promise that resolves after a delay
function delay(t) {
return new Promise(function (resolve) {
setTimeout(resolve, t);
});
}
Promise.delay = function (fn, t) {
// fn is an optional argument
if (!t) {
t = fn;
fn = function () {};
}
return delay(t).then(fn);
}
Promise.prototype.delay = function (fn, t) {
// return chained promise
return this.then(function () {
return Promise.delay(fn, t);
});
}
function log(args) {
var str = "";
for (var i = 0; i < arguments.length; i++) {
if (typeof arguments[i] === "object") {
str += JSON.stringify(arguments[i]);
} else {
str += arguments[i];
}
}
var div = document.createElement("div");
div.innerHTML = str;
var target = log.id ? document.getElementById(log.id) : document.body;
target.appendChild(div);
}
function log1() {
log("Message 1");
}
function log2() {
log("Message 2");
}
function log3() {
log("Message 3");
}
Promise.delay(log1, 500).delay(log2, 700).delay(log3, 600);
The functions you supply to this version can either by synchonrous or asynchronous (returning a promise).
Inspired by #jfriend00 I demonstrated a shorter version:
Promise.resolve()
.then(() => delay(400))
.then(() => log1())
.then(() => delay(500))
.then(() => log2())
.then(() => delay(800))
.then(() => log3());
function delay(duration) {
return new Promise((resolve) => {
setTimeout(resolve, duration);
});
}
function log1() {
console.log("Message 1");
}
function log2() {
console.log("Message 2");
}
function log3() {
console.log("Message 3");
}
If your using Typescript targeting ES6 this is pretty simple with Async Await. This is also very very easy to read and a little upgrade to the promises answer.
//WARNING: this is Typescript source code
//expect to be async
async function timePush(...arr){
function delay(t){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve();
},t)
})
}
//for the length of this array run a delay
//then log, you could always use a callback here
for(let i of arr){
//pass the items delay to delay function
await delay(i.time);
console.log(i.text)
}
}
timePush(
{time:1000,text:'hey'},
{time:5000,text:'you'},
{time:1000,text:'guys'}
);
I have encountered the same issue. My solution was to call self by setTimeout, it works.
let a = [[20,1000],[25,5000],[30,2000],[35,4000]];
function test(){
let b = a.shift();
console.log(b[0]);
if(a.length == 0) return;
setTimeout(test,b[1]);
}
the second element in array a is time to be delayed
Using async / await with #Penny Liu example:
(async() => {
await delay(400)
log1()
await delay(500)
log2()
await delay(800)
log3()
})()
async function delay(duration) {
return new Promise((resolve) => {
setTimeout(resolve, duration);
});
}
function log1() {
console.log("Message 1");
}
function log2() {
console.log("Message 2");
}
function log3() {
console.log("Message 3");
}