What is the best way to handle function interrupts in javascript - 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.

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");
}
})();

Sort of await setInterval

I need to re-execute a hand made for loop (because I need time between each step) 10s after it ends forever.
So after few tries, I came with this code, but it doesn't work because it re-executes every 10s, not after the loop finishes. I tried to but async function in the interval and put all my code in an await one, but it didn't work.
async function refreshEDT() {
let rows = appModel.getAll()
let orderFiliere = {};
await rows.then((res) => {
for (let i = 0; i < res.length; i++) {
if (res[i].filiere in orderFiliere) {
orderFiliere[res[i].filiere].push(res[i])
} else {
orderFiliere[res[i].filiere] = [res[i]]
}
}
})
return orderFiliere
}
let edt = refreshEDT()
setInterval(() => {
edt = refreshEDT()
}, 1000 * 60 * 60) //ms to s to m to h
setInterval(() => {
edt.then((lessons) => {
(function loop(i) {
let lessons_key = Object.keys(lessons)
setTimeout(() => {
// processing things
if (--i) loop(i);
}, 1000 * 10) //ms to s to 10s
})(Object.keys(lessons).length - 1)
})
console.log(1)
}, 1000 * 10) //ms to s to 10s
Do you have any solution?
Ty!
EDIT
Let's explain what I try to do:
I get things on my DB, try to classify by "filiere", one of my column, and return that object.
Those data are reloaded every hour.
After that, I need to emit with socket.io every "filiere" with 10s between, and repeat that.
Perhaps the code will be easier to read when you wrap setTimeout with a promise to await.
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function refreshEDT() {
const res = await appModel.getAll();
const orderFiliere = res.reduce((acc, value) => {
const { filiere } = value;
if (filiere in acc) {
acc[filiere].push(value);
} else {
acc[filiere] = [value];
}
return acc;
}, {});
return orderFiliere;
}
// consider naming this
(async () => {
let edt = refreshEDT();
setInterval(() => {
edt = refreshEDT();
}, 1000 * 60 * 60);
while (true) {
const lessons = await edt;
for (const lessons_key of Object.keys(lessons)) {
await sleep(1000 * 10);
// processing things
}
// repeat after 10 seconds
await sleep(1000 * 10);
}
})();
With ES2022, you can use Array.prototype.groupBy() to simplify your refreshEDT() implementation:
async function refreshEDT() {
const res = await appModel.getAll();
const orderFiliere = res.groupBy(({ filiere }) => filiere);
return orderFiliere;
}
You could try using setTimeout to call your function again.
const myFunc = () => {
// processing things
setTimeout(myFunc , 10000)
}
myFunc();
I think this could help if i understood the problem correctly :
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async yourFunction() {
const secondsToWait: number = 10;
let i = 0;
for (let item of List) {
// Do stuff
if (i === list.length -1) {
await wait(secondsToWait*1000);
yourFunction();
}
i++;
}
}

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.

How can I break out of this Promise for loop?

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

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