How can I break out of this Promise for loop? - javascript

EDIT: I know how to get it working using async/await but unfortunately, I can't use it as it's not widely supported (especially iOS 10, Internet Explorers etc). So I need a promises only way.
I am very new to Promises so I am stuck. I have this for loop which I want to break out of whenever i equals to 2 (just for example). I have tried putting the reject but it still continues on until i<10. I want it to stop when my condition is met.
for (let i = 0, p = Promise.resolve(); i < 10; i++) {
p = p.then(_ => new Promise((resolve, reject) => {
setTimeout(function() {
console.log(i);
if (i == 2) {
reject("BREAK HERE");
} else {
resolve();
}
}, Math.random() * 1000)
})).catch(err => {
console.log(err);
});
}
Current output:
0
1
2
BREAK HERE
3
4
5
6
7
8
9
I want it to just be:
0
1
2
BREAK HERE

Just dont resolve and the sequence will stop
for (let i = 0, p = Promise.resolve(); i < 10; i++) {
p = p.then(_ => new Promise((resolve, reject) => {
setTimeout(function() {
console.log("i",i);
if (i == 2) {
console.log("break here")
} else {
resolve();
}
}, Math.random() * 1000)
})).catch(err => {
console.log("err",err);
});
}
But as this code itself intents to sequence async calls one should think about doing so:
// repeated call
next(
0/*state*/,
i=>i<2/*match*/,
i=>i+1/*increment*/,
asyncCallPromisefied/*promise|promise[]*/,
[0]/*args*/
)
.catch(console.log)
// traverse array
next(
0/*state*/,
i => i < 6/*match*/,
i => i + 1/*increment*/,
asyncChain()/*promise|promise[]*/,
[0]/*args*/,
0/*[promise[] index]*/
)
.catch(console.log)
// helper
function next(state, match, increment, action, args, pointer = 0/**inactive**/){
if(match(state))
if(Array.isArray(action)){
return pointer < action.length
? action[pointer](...args)
.then(i => next(
increment(state),
match,
increment,
action,
[i],
pointer + 1
))
: undefined;
} else {
return action(...args)
.then(i => next(
increment(state),
match,
increment,
action,
[i]
))
}
}
// wrapper
function asyncCall(i, callback){
return setTimeout(() => {
console.log(i == 2 ? 'break here': i);
callback(null, ++i);
}, Math.random() * 1000)
}
function asyncCallPromisefied(i){
return new Promise(function(resolve, reject){
asyncCall(i, function(err, args){
if(err){
reject(err)
} else {
resolve(args)
}
})
})
}
function asyncChain(){return [
function(i){console.log("chain",i);return Promise.resolve(i)},
function(i){console.log("chain",i);return Promise.resolve(i)},
function(i){console.log("chain",i);return Promise.resolve(i)},
asyncCallPromisefied,
function(i){console.log("chain",i);return Promise.resolve(i)},
]}

Remove the catch, otherwise, the error will be caught and handled. To log rather than error, use console.log:
try {
for (let i = 0, p = Promise.resolve(); i < 10; i++) {
p = p.then(_ => new Promise((resolve, reject) => {
setTimeout(function() {
console.log(i);
if (i == 2) {
reject(console.log("BREAK HERE"));
} else {
resolve();
}
}, Math.random() * 1000)
}));
}
} catch (e) {}

As I understand your problem, you want to sequentially work one promise after anothe. You then want to abort as soon as the first promise fails.
To ensure execution order, you have to do it like this in a for loop, though you lose the actual benefit of concurrent runtime. This code will run sequentually:
const delay = async () => new Promise((resolve) => setTimeout(() => resolve(), Math.random() * 1e3);
const myUpload = async (file) => {
await delay();
if (file === 2) {
return Promise.reject(`COULD NOT UPLOAD ${file}`);
} else {
console.log("uploaded file", file);
return;
}
};
const uploadFilesSequentually = async (files) => {
for (const file of files) {
await myUpload(file); // this will create, and wait for the promise to finish or fail, only then the for loop continues
}
};
If you were not to care about the order of execution and could allow for concurrency, I'd do something like this:
const promises = myFiles.map(myUpload); // all promises are now pending and will resolve in different orders
Promise.all(promises) // returns its own promise then resolves once all of them have succeded or is rejected as soon as one is rejected
.then((result) => console.log("everything uploaded"))
.catch(console.error);
(The result of Promise.all would keep the order of the results though. So even if the third promise resolves before the first, the result would be [resultPromise1, resultPrimise2, resultPromise3, ... ])

For now, I ended up using a different way to achieve this without Promises:
var i=0;
function fetchNext(){
if (i==2) {
console.log("BREAK HERE");
} else {
setTimeout(function () {
console.log(i);
i++;
fetchNext();
}, Math.random() * 1000);
}
}
fetchNext();

remove the .catch in the loop and deal with it after.
let p = Promise.resolve();
for (let i = 0; i < 10; i++) {
p = p.then(_ => new Promise((resolve, reject) => {
setTimeout(function() {
console.log(i);
if (i == 2) {
reject("BREAK HERE");
} else {
resolve();
}
}, Math.random() * 1000)
}));
}
p.catch(err => {
console.log(err);
})

In any case, your code will always execute 10 times, since setTimeout is asynchronous operation and till the time your condition reject("BREAK HERE") is met, already promise is chained ten times.
This is what you can do, though it looks messy :(
for (let i = 0, p = Promise.resolve(); i < 10; i++) {
p = p.then(data => new Promise((resolve, reject) => {
setTimeout(function() {
if(data && data.isRejected){
resolve({ isRejected: true })
return;
}
console.log(i);
if (i == 2) {
reject("BREAK HERE");
} else {
resolve({});
}
}, Math.random() * 1000)
})).catch(err => {
console.log(err);
/* add this line */
return { isRejected: true };
});
}
Returning a object from catch(instead of throwing) will mark the promise as resolved. more in docs
This will provide you desired result

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.

Trying to set a recursive function inside a promise

let cancelDownload = true
const delay = () => {
return new Promise(() => {
if (cancelDownload === true){
setTimeout(() => {
console.log('Delaying...');
delay();
}, 1000);
}
else return null;
});
};
const cancelJob = async() => {
for(let i = 6; i>0; i--){
console.log('inside for ',i);
setTimeout(()=>{
cancelDownload = false
},4000)
await delay()
console.log('aaaaaaaa');
console.log(`the number is ${i}`);
}
}
cancelJob()
I am trying to write a delay function whereby once the condition is met delay is removed and all code is resumed but it seems that once the delay is done the code exits without executing the last two console logs
No recursion needed. Instead, you can:
Use setInteval to check your condition every second.
When the condition is correct, you need to resolve the promise.
Use clearInterval.
let cancelDownload = true
const delay = () => {
let intervalId;
return new Promise((resolve, reject) => {
const check = () => {
if (cancelDownload === true){
console.log('Delaying...');
} else {
clearInterval(intervalId);
resolve();
}
}
//check immediately
check();
//then check every second afterwards
intervalId = setInterval(check, 1000);
});
};
const cancelJob = async() => {
for(let i = 6; i>0; i--){
console.log('inside for ',i);
setTimeout(()=>{
cancelDownload = false
},4000)
await delay()
console.log('aaaaaaaa');
console.log(`the number is ${i}`);
}
}
cancelJob()
This can be generalised a bit in the following fashion - instead of hard-coding the condition, supply it as a callback. Then you can have a delay function without using global variables and it can wait for different things, not just one single variable.
const delayWhile = shouldWait => () => {
let intervalId;
return new Promise((resolve, reject) => {
const check = () => {
if (shouldWait()){
console.log('Delaying...');
} else {
clearInterval(intervalId);
resolve();
}
}
//check immediately
check();
//then check every second afterwards
intervalId = setInterval(check, 1000);
});
};
const cancelJob = async() => {
let cancelDownload = true;
const delay = delayWhile(() => cancelDownload);
for(let i = 6; i>0; i--){
console.log('inside for ',i);
setTimeout(()=>{
cancelDownload = false
},4000)
await delay()
console.log('aaaaaaaa');
console.log(`the number is ${i}`);
}
}
cancelJob()
The function you pass to the promise constructor (the promise executor function) is called with two arguments: a function to use to resolve the promise and a function to reject it (conventionally called resolve and reject). Your code should call one of those functions when your asynchronous work is done.
Yours isn't, so the promise never settles, and your code waits forever at the await.
But there are other issues:
1. If you call delay again, it creates a new promise. Your code using await only has access to the first promise, not the ones created by those recursive calls. There isn't really any reason to use recursion here at all.
2. All calls to the function share the same flag. So if we fix the issue with not fulfilling the promise, the loop does wait, but only once:
let cancelDownload = false;
const delay = () => {
return new Promise((resolve) => {
tick();
function tick() {
// Cancelled?
if (cancelDownload) {
// Yes, fulfill the promise
console.log("Flag is set, fulfilling");
resolve(null);
} else {
// No, wait another second
console.log("waiting 1000 and checking again");
setTimeout(tick, 1000);
}
}
});
};
const cancelJob = async () => {
setTimeout(() => {
console.log("Setting cancelDownload to true");
cancelDownload = true;
}, 4000);
for (let i = 6; i > 0; i--) {
console.log("inside for ", i);
await delay();
console.log("aaaaaaaa");
console.log(`the number is ${i}`);
}
};
cancelJob();
.as-console-wrapper {
max-height: 100% !important;
}
I may be mistaken that that second one is a problem for your use case, though, since in a comment on a now-deleted answer you said you wanted the loop only to wait once (the "whole loop" rather than just one iteration).
If you want a function that polls a flag (I don't recommend it, polling is generally not best practice, though sometimes you can't avoid it) and fulfills a promise when it's set, you could use AbortController:
const delay = (signal) => {
let done = false;
return new Promise((resolve) => {
tick();
function tick() {
// Cancelled?
if (signal.aborted) {
// Yes, fulfill the promise with null
console.log("Fulfilling with null");
resolve(null);
} else {
// No, wait another second
console.log("Waiting 1000 and checking again");
setTimeout(tick, 1000);
}
}
});
};
const cancelJob = async () => {
const controller = new AbortController();
setTimeout(() => {
console.log("Cancelling");
controller.abort();
}, 4000);
for (let i = 6; i > 0; i--) {
console.log("inside for ", i);
await delay(controller.signal);
console.log("aaaaaaaa");
console.log(`the number is ${i}`);
}
};
cancelJob();
.as-console-wrapper {
max-height: 100% !important;
}
That also only delays the loop once, because we're passing the same signal to all of the delay functions. Originally I was creating that inside the loop, like this:
const delay = (signal) => {
let done = false;
return new Promise((resolve) => {
tick();
function tick() {
// Cancelled?
if (signal.aborted) {
// Yes, fulfill the promise with null
console.log("Fulfilling with null");
resolve(null);
} else {
// No, wait another second
console.log("Waiting 1000 and checking again");
setTimeout(tick, 1000);
}
}
});
};
const cancelJob = async () => {
for (let i = 6; i > 0; i--) {
const controller = new AbortController();
setTimeout(() => {
console.log("Cancelling");
controller.abort();
}, 4000);
console.log("inside for ", i);
await delay(controller.signal);
console.log("aaaaaaaa");
console.log(`the number is ${i}`);
}
};
cancelJob();
.as-console-wrapper {
max-height: 100% !important;
}
...but then I saw the "whole loop" comment on the other answer.
Note: Normally when you have an asynchronous process with a cancel feature like that, it rejects on cancel with a cancel-specific rejection reason rather than fulfilling, but you get the idea.
One approach to cancellable promises is to supply the promise creator function with a writable parameter like cancelToken. The promise creator populates this parameter with a callback, which, when invoked, cancels this particular promise. This usually leads to simpler and more linear code.
let pause = () => new Promise(r => setTimeout(r, 100));
async function job(name, steps, cancelToken) {
let cancelled = false;
cancelToken.callback = () => cancelled = true;
for (let step = 1; step <= steps; step++) {
console.log(name, step);
await pause();
if (cancelled) {
console.log('CANCELLED');
break;
}
}
}
async function main() {
let cancelToken = {};
let job1 = job('ok', 5, cancelToken);
await job1;
let job2 = job('bad', 5, cancelToken);
setTimeout(cancelToken.callback, 100);
await job2;
}
main()
As a general hint, it's better to avoid new Promise and callbacks whether possible. Note how in this example Promise and setTimeout are only used for testing purposes (simulate real work) and the rest of the code won't change once we replace pause with something more useful like fetch.

Use setTimeout inside while loop in Node.js

I want to hold nodejs execution in setTimeout inside while loop. I have used async- waterfall function but it didn't work inside while loop. so I have used below code :-
var i = 3;
while(i> 0)
{
setTimeout(function(){
console.log('start', i);
setTimeout(function(){ console.log('timeout');}, 1000);
console.log('end', i);
i--;
}, 1000);
}
console.log("execution ends");
But I didn't get the expected output.
My Expected output will be like :-
start3
timeout
end3
start2
timeout
end2
start1
timeout
end1
execution ends
way 1:use while loop
var i = 3
var p = Promise.resolve(i)
while (i > 0) {
(i => {
p = p.then(() => {
return new Promise(function (resolve, reject) {
console.log('start', i)
setTimeout(function () {
console.log('timeout')
console.log('end', i)
resolve()
}, 1000)
})
})
})(i)
i--
}
p = p.then(data => console.log('execution ends'))
way2:
function test(i) {
console.log('start', i)
setTimeout(() => {
console.log('timeout')
console.log('end', i)
i--
if (i < 0) {
return
}
test(i)
}, 1000)
}
test(3);
way3:use async
async function test (i) {
console.log('start', i)
await new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('timeout')
console.log('end', i)
i--
if (i < 0) {
reject(i)
}
resolve(i)
}, 1000)
})
.then(i => test(i))
.catch(i => console.log('done', i))
}
test(3)
nodejs is async by nature and setTimeout is more or less like executing something on a new thread (more or less because JS is single threaded and uses event loop).
Check out this npm package :
https://www.npmjs.com/package/sleep
That should do the trick...
But obviously you should use sleep only for debugging purposes.
In production code you'd better embrase the async nature of NodeJS and use Promises
Check out this for more details:
event loop : https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
setTimeout : https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout
Promise : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
There are a few issues with your program w.r.t to your expected output. The first i-- will work only after 1000ms. By that time, too many while loops would have run. Also setTimeOut is non-blocking ,hence ,the execution ends will be consoled even before the first start is consoled . To reach your expected outcome, you can make a few changes to the code :
var i = 3;var j =3;
while(j> 0)
{
setTimeout(function(){
console.log('start', i);
console.log('timeout');
console.log('end', i);
i--;
}, 1000);
j--;
}
if(j == 0){
setTimeout(function(){
console.log('execution ends');
}, 1000);
}
Please try below snippet. You can introduce API calls OR async calls in place of setTimeout in timer method.
const timer = () => {
return new Promise(res => {
setTimeout(() => {
console.log('timeout');
res();
}, 1000);
});
}
let i = 3;
let temp;
while (i > 0) {
if (temp !== i) {
temp = i;
console.log('start', temp);
timer().then(() => {
console.log('end', temp);
i -= 1;
});
}
}
First off, you have to understand that setTimeout() in Javascript is non-blocking. That means that all it does is schedule something to run later and then the rest of your code immediately keeps on running.
In your particular code example, you will have an infinite loop because the while loop keeps going forever, continuing to schedule more and more timers, but until the while loop stops, none of those timers can run. And, until one of those timers can run, your loop variable i never gets changed so the while loop never stops.
To understand why it works this way you really have to understand the event-driven design of Javascript and node.js. When you call setTimeout(), it schedules an internal timer inside of the JS engine. When that timer fires, it inserts an event in the Javascript event queue. The next time the JS interpreter is done with what it was doing, it will check the event queue and pull the next event out of the queue and run it. But, your while loop never stops going so it can't ever get to any new events, thus it can never run any of your timer events.
I will show you three different ways to generate your desired output and they are all in runnable code snippets so you can run them right in the answer to see their results.
The first technique is accomplished in plain Javascript just by setting timers at different times from the future such that the various desired outputs trigger in the right sequence:
Varying Timers
let cntr = 3;
for (let i = 1; i <= 3; i++) {
// schedule timers at different increasing delays
setTimeout(function() {
console.log('start', cntr);
setTimeout(function() {
console.log('timeout');
console.log('end', cntr);
--cntr;
if (cntr === 0) {
console.log("execution ends");
}
}, 1000);
}, i * 2000);
}
Or, if you really want it to be a while loop:
let cntr = 3, i = 1;
while (i <= 3) {
// schedule timers at different increasing delays
setTimeout(function() {
console.log('start', cntr);
setTimeout(function() {
console.log('timeout');
console.log('end', cntr);
--cntr;
if (cntr === 0) {
console.log("execution ends");
}
}, 1000);
}, i * 2000);
i++;
}
Using Promises to Sequence Operations
function delay(t, v) {
return new Promise(resolve => {
setTimeout(resolve.bind(null, v), t);
});
}
function run(i) {
return delay(1000).then(() => {
console.log("start", i);
return delay(1000).then(() => {
console.log("timeout");
console.log("end", i);
return i - 1;
});
});
}
run(3).then(run).then(run).then(() => {
console.log("execution ends");
});
This could also be put into a loop if you wanted:
function delay(t, v) {
return new Promise(resolve => {
setTimeout(resolve.bind(null, v), t);
});
}
function run(i) {
return delay(1000).then(() => {
console.log("start", i);
return delay(1000).then(() => {
console.log("timeout");
console.log("end", i);
return i - 1;
});
});
}
[3, 2, 1].reduce((p, v) => {
return p.then(() => {
return run(v);
});
}, Promise.resolve()).then(() => {
console.log("execution ends");
});
Promises Plus ES7 Async/Await
This method uses the await feature of ES7 to allow you to write sequential-like code using promises and await which can make these kinds of loops a lot simpler. await blocks the internal execution of a function while it waits for a promise to resolve. It does not block the external return of the function. the function still returns immediately. It returns a promise that resolves when all the blocked pieces of the internal function are done. That's why we use .then() on the result of runSequence() to know when it's all done.
function delay(t, v) {
return new Promise(resolve => {
setTimeout(resolve.bind(null, v), t);
});
}
async function runSequence(num) {
for (let i = num; i > 0; i--) {
await delay(1000);
console.log("start", i);
await delay(1000);
console.log("timeout");
console.log("end", i);
}
}
runSequence(3).then(() => {
console.log("execution ends");
});
This await example illustrates how promises and await can simplify the sequencing of operations in ES7. But, they still require you to understand how asynchronous operations work in Javascript, so please don't attempt to skip that level of understanding first.
you didn't got the expected output because there is closure in your code,improve your code like this:
var i = 3;
while(i> 0)
{
setTimeout((
function(i){
return function(){
console.log('start', i);
setTimeout(function(){ console.log('timeout');}, 1000);
console.log('end', i);
i--;
}
}
)(i), 1000);
}

ES6 Promise.all progress

I have several promises that I need to resolve before going further.
Promise.all(promises).then((results) => {
// going further
});
Is there any way I can have the progress of the Promise.all promise?
From the doc, it appears that it is not possible. And this question doesn't answer it either.
So:
Don't you agree that this would be useful? Shouldn't we query for this feature?
How can one implement it manually for now?
I've knocked up a little helper function that you can re-use.
Basically pass your promises as normal, and provide a callback to do what you want with the progress..
function allProgress(proms, progress_cb) {
let d = 0;
progress_cb(0);
for (const p of proms) {
p.then(()=> {
d ++;
progress_cb( (d * 100) / proms.length );
});
}
return Promise.all(proms);
}
function test(ms) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Waited ${ms}`);
resolve();
}, ms);
});
}
allProgress([test(1000), test(3000), test(2000), test(3500)],
(p) => {
console.log(`% Done = ${p.toFixed(2)}`);
});
You can add a .then() to each promise to count whos finished.
something like :
var count = 0;
var p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 5000, 'boo');
});
var p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 7000, 'yoo');
});
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 3000, 'foo');
});
var promiseArray = [
p1.then(function(val) {
progress(++count);
return val
}),
p2.then(function(val) {
progress(++count);
return val
}),
p3.then(function(val) {
progress(++count);
return val
})
]
function progress(count) {
console.log(count / promiseArray.length);
}
Promise.all(promiseArray).then(values => {
console.log(values);
});
This has a few advantages over Keith's answer:
The onprogress() callback is never invoked synchronously. This ensures that the callback can depend on code which is run synchronously after the call to Promise.progress(...).
The promise chain propagates errors thrown in progress events to the caller rather than allowing uncaught promise rejections. This ensures that with robust error handling, the caller is able to prevent the application from entering an unknown state or crashing.
The callback receives a ProgressEvent instead of a percentage. This eases the difficulty of handling 0 / 0 progress events by avoiding the quotient NaN.
Promise.progress = async function progress (iterable, onprogress) {
// consume iterable synchronously and convert to array of promises
const promises = Array.from(iterable).map(this.resolve, this);
let resolved = 0;
// helper function for emitting progress events
const progress = increment => this.resolve(
onprogress(
new ProgressEvent('progress', {
total: promises.length,
loaded: resolved += increment
})
)
);
// lift all progress events off the stack
await this.resolve();
// emit 0 progress event
await progress(0);
// emit a progress event each time a promise resolves
return this.all(
promises.map(
promise => promise.finally(
() => progress(1)
)
})
);
};
Note that ProgressEvent has limited support. If this coverage doesn't meet your requirements, you can easily polyfill this:
class ProgressEvent extends Event {
constructor (type, { loaded = 0, total = 0, lengthComputable = (total > 0) } = {}) {
super(type);
this.lengthComputable = lengthComputable;
this.loaded = loaded;
this.total = total;
}
}
#Keith in addition to my comment, here is a modification
(edited to fully detail hopefuly)
// original allProgress
//function allProgress(proms, progress_cb) {
// let d = 0;
// progress_cb(0);
// proms.forEach((p) => {
// p.then(()=> {
// d ++;
// progress_cb( (d * 100) / proms.length );
// });
// });
// return Promise.all(proms);
//}
//modifying allProgress to delay 'p.then' resolution
//function allProgress(proms, progress_cb) {
// let d = 0;
// progress_cb(0);
// proms.forEach((p) => {
// p.then(()=> {
// setTimeout( //added line
// () => {
// d ++;
// progress_cb( (d * 100) / proms.length );
// }, //added coma :)
// 4000); //added line
// });
// });
// return Promise.all(proms
// ).then(()=>{console.log("Promise.all completed");});
// //added then to report Promise.all resolution
// }
//modified allProgress
// version 2 not to break any promise chain
function allProgress(proms, progress_cb) {
let d = 0;
progress_cb(0);
proms.forEach((p) => {
p.then((res)=> { //added 'res' for v2
return new Promise((resolve) => { //added line for v2
setTimeout( //added line
() => {
d ++;
progress_cb( (d * 100) / proms.length );
resolve(res); //added line for v2
}, //added coma :)
4000); //added line
}); //added line for v2
});
});
return Promise.all(proms
).then(()=>{console.log("Promise.all completed");});
//added then chaining to report Promise.all resolution
}
function test(ms) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Waited ${ms}`);
resolve();
}, ms);
});
}
allProgress([test(1000), test(3000), test(2000), test(3500)],
(p) => {
console.log(`% Done = ${p.toFixed(2)}`);
});
"Promise.all completed" will output before any progress message
here is the output that I get
% Done = 0.00
Waited 1000
Waited 2000
Waited 3000
Waited 3500
Promise.all completed
% Done = 25.00
% Done = 50.00
% Done = 75.00
% Done = 100.00
Here's my take on this. You create a wrapper for the progressCallback and telling how many threads you have. Then, for every thread you create a separate callback from this wrapper with the thread index. Threads each report through their own callback as before, but then their individual progress values are merged and reported through the wrapped callback.
function createMultiThreadProgressWrapper(threads, progressCallback) {
var threadProgress = Array(threads);
var sendTotalProgress = function() {
var total = 0;
for (var v of threadProgress) {
total = total + (v || 0);
}
progressCallback(total / threads);
};
return {
getCallback: function(thread) {
var cb = function(progress) {
threadProgress[thread] = progress;
sendTotalProgress();
};
return cb;
}
};
}
// --------------------------------------------------------
// Usage:
// --------------------------------------------------------
function createPromise(progressCallback) {
return new Promise(function(resolve, reject) {
// do whatever you need and report progress to progressCallback(float)
});
}
var wrapper = createMultiThreadProgressWrapper(3, mainCallback);
var promises = [
createPromise(wrapper.getCallback(0)),
createPromise(wrapper.getCallback(1)),
createPromise(wrapper.getCallback(2))
];
Promise.all(promises);
You can use my npm package with an extended version of the native promise, that supports advanced progress capturing, including nested promises, out of the box Live sandbox
import { CPromise } from "c-promise2";
(async () => {
const results = await CPromise.all([
CPromise.delay(1000, 1),
CPromise.delay(2000, 2),
CPromise.delay(3000, 3),
CPromise.delay(10000, 4)
]).progress((p) => {
console.warn(`Progress: ${(p * 100).toFixed(1)}%`);
});
console.log(results); // [1, 2, 3, 4]
})();
Or with concurrency limitation (Live sandbox):
import { CPromise } from "c-promise2";
(async () => {
const results = await CPromise.all(
[
"filename1.txt",
"filename2.txt",
"filename3.txt",
"filename4.txt",
"filename5.txt",
"filename6.txt",
"filename7.txt"
],
{
async mapper(filename) {
console.log(`load and push file [${filename}]`);
// your async code here to upload a single file
return CPromise.delay(1000, `operation result for [${filename}]`);
},
concurrency: 2
}
).progress((p) => {
console.warn(`Uploading: ${(p * 100).toFixed(1)}%`);
});
console.log(results);
})();
const dataArray = [];
let progress = 0;
Promise.all(dataArray.map(async (data) => {
await something();
console.log('progress = ', Math.celi(progress++ * 100 / dataArray.length))
}))

Categories