I have a situation in my node.js program where I have an array of promises. I am prepared to wait a maximum of 200 ms for each promise in the array to get fulfilled, if it’s not fulfilled by then I want it to be rejected.
The code I have written for this works when I run my script in the terminal using node.js without a debugger attached.
However, when I debug the same script using VS code it stops as soon as a promise gets rejected due to timeout. The debugger claims that the rejection is an uncaught exception.
How can I change the code I have such that it does exactly what it does now, but a rejected promise doesn’t cause an exception?
I have tried adding try{} catch{} all over the place but cannot seem to find a solution.
Here is a minimal reproducible example of my issue (the debugger complains about the line reject( "timeout" ) ):
async function delayedPromise(delay) {
await new Promise((res) => setTimeout(res, delay));
return "success";
}
function rejectAfterDelay(ms) {
return new Promise((_, reject) => setTimeout(() => {
reject("timeout");
}, ms));
}
async function main() {
// Create array of promises.
promArr = [];
promArr.push(delayedPromise(100));
promArr.push(delayedPromise(200));
promArr.push(delayedPromise(300));
promArr.push(delayedPromise(400));
promArr.push(delayedPromise(500));
// Wait for all promises to either get fulfilled or get rejected after 200 ms.
const msMaxTime = 200;
const result = await Promise.allSettled(
promArr.map(promise => Promise.race([promise, rejectAfterDelay(msMaxTime)]))
);
console.log(result);
}
main()
Instead of racing a promise with a short-lived promise(rejectAfterDelay), we can wrap the promise in a short-lived promise:
async function delayedPromise(delay) {
return new Promise((res) => setTimeout(res, delay, 'success'));
}
// wrap the promise instead of racing it
function rejectAfterDelay(promise, ms) {
return new Promise((resolve, reject) => {
setTimeout(reject, ms, 'timeout');
// forward the reasons to the wrapper
promise.then(reason => resolve(reason))
.catch(err => reject(err));
});
}
async function main() {
// Create array of promises.
promArr = [];
promArr.push(delayedPromise(100));
promArr.push(delayedPromise(200));
promArr.push(delayedPromise(300));
promArr.push(delayedPromise(400));
promArr.push(delayedPromise(500));
// Wait for all promises to either get fulfilled or get rejected after 200 ms.
const msMaxTime = 200;
const result = await Promise.allSettled(
promArr.map(promise => {
//return Promise.race([promise, rejectAfterDelay(msMaxTime)]);
return rejectAfterDelay(promise, msMaxTime);
})
);
console.log(result.map(r => r.value ? r.value : r.reason));
}
main()
With this the debugger doesn't complain when Uncaught Exceptions option is selected.
Also, depending on your situation, instead of setTimeout(reject, ms, 'timeout') you can use setTimeout(resolve, ms, 'timeout') to make it fail gracefully.
Related
I have a async function that awaits a promise which resolves when it receives some 'data'. However, when I run the test, I get a Error: Timeout of 300000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
Here is my code snippet, I am using this in truffle to test solidity contracts :
contract("Test", async (accounts) => {
it("test description", async () => {
let first = await getFirstEvent(oracle.LogResult({fromBlock:'latest'}));
let second = await getFirstEvent(oracle.LogResult({fromBlock:'latest'}));
Promise.all([first,second]);
//some assertion code
});
const getFirstEvent = (_event) => {
return new Promise((resolve, reject) => {
_event.once('data', resolve).once('error', reject)
});
}
});
Isn't the promise resolving ? I can see 'data' coming back in the callback because I am emitting the callback event in the solidity code I am testing.
I managed to resolve this issue, so posting it here so that others can use the approach.
I created a Promise that times out after a duration we can set :
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {a
reject(new Error('Request timed out'));
}, 200000);
})
Then, I race the timeoutPromise with the Promise which is fetching data, like this for the case I posted :
Promise.race([getFirstEvent(oracle.LogResult({fromBlock:'latest'})), timeoutPromise]);
It looks to me like there's a few things wrong here.
First of all, your function isn't returning anything i.e. it should be return Promise.all([first, second]);.
Secondly, if the goal of the promise.all is to execute the promises in parallel, then that's not what it's doing here because you already have await statements on those function calls above. What you are looking for here would be:
return await Promise.all([
getFirstEvent(oracle.LogResult({fromBlock:'latest'}),
getFirstEvent(oracle.LogResult({fromBlock:'latest'})]);
Now in terms of the promise not resolving, I'm assuming the event is generated from oracle.LogResult(). In this case, what you'd want to do is setup your promises to listen for the event first, for example:
let first = getFirstEvent();
let second = getSecondEvent();
Now you have 2 promises that are listening for the events. Next, you generate the event:
oracle.LogResult({ fromBlock: 'latest' });
oracle.LogResult({ fromBlock: 'latest' });
Finally, you ensure you wait on the result of the promises:
return await Promise.all([first, second]);
I want to reject a promise which I do not built. That is, examples I've read describe something like it:
const sample = new Promise((resolve, reject) => {
setTimeout(() => {
reject('fail promise');
}, 1000);
});
That reject the sample after 1s. In my case the promise I want to reject is coming as an external api call then I can't to reject in that way.
Another approaches I've read shows how the promise can be wrapped with other that uses a setTimeout to reject the new promise. Something like this:
const timeout = new Promise(function(resolve, reject) {
setTimeout(resolve, 1000, 'one');
});
const sample = new Promise(function(resolve, reject) {
setTimeout(resolve, 5000, 'two');
});
return Promise.race([sample, timeout]);
That force to 'reject' sample after 1s returning the other promise. That could be used to set a timeout but in fact it does not reject sample, only returns another promise and the original continues running until it rejects or resolve itself.
I can not find how can I reject correctly a promise without a library
Why do you need to reject their promise? What you care about is getting a useful result for your code, so write code that gets you that result instead.
Wrap the API call in your own promise, with a timeout rejection on your promise, and a pass-through resolve if that API yields a result before the timeout:
const attempt = new Promise( (resolve, reject) => {
// run the actual API _and_ your rejection timeout concurrently:
let rejected = false;
const id = setTimeout(() => {
rejected = true;
reject(new Error('timeout');
}), 1000);
actualApiCall(some, input, args, here)
.then(result => {
if (rejected) return; // <- if we rejected via timeout, we no longer care.
clearTimeout(id); // <- always remember to properly clean up
resolve(result);
})
.catch(e => {
if (rejected) return;
clearTimeout(id);
reject(e)
});
});
attempt
.then(result => doSomethingWith(result))
.catch(e => console.log('rejected:', e));
I can not find how can I reject correctly a promise without a library
First of all, this doesn't seems a good approach to me, even though you can do the following,
const apiPromise = externalApi();
Now apiPromise can either be in resolved or rejected state. In both cases you can throw an error like,
const rejectedPromise = apiPromise.then(
(fulfilledValue) => { throw "error" },
(rejetedValue) => { throw "error" }
);
rejectedPromise.catch(err => console.log(err)); //logs "error"
Read more on Promise.then()
Promises just baffle me.
I'm trying to make a mock data service to imitate axios.
My mock put call passes a targetUrl to _fetch which then sees if it's a valid url and either returns a new Promise with a delayed .resolve
const _returnResponse = (mockData, time = 0) => new Promise((resolve) => {
setTimeout(() => {
resolve(mockData);
}, time);
});
or a new Promise with a delayed .reject
const _returnError = (time = simulatedDelay) => {
const returnValue = new Promise(((resolve, reject) => {
setTimeout(() => {
reject(new Error('error'));
}, time);
}));
return returnValue;
};
but when I make my mock put call this returns a mock data that the calling method interprets as a success and console logs in its .then
put(target, putBody) {
const returnValue = _fetch(target, simulatedDelay)
returnValue.then(response => _console('PUT', target, response, putBody));
return returnValue;
},
But with an invalid target console logs an uncaught error
or this handles the error correctly, but console logs an undefined response
put(target, putBody) {
const returnValue = _fetch(target, simulatedDelay).then(response => _console('PUT', target, response, putBody));
return returnValue;
},
Here's the calling method:
saveStuff({ commit, state }, newStuff) {
//other code
return this.$mockAxios.put(url, putBody)
.then((response) => {
return response;
});
},
I feel like I'm completely missing something and I've researched this for hours and I'm still not getting it.
As a direct answer to the question: yes, you can add .then() to a promise after it's created.
Example:
const hi = new Promise((resolve, reject) => {
setTimeout(() => resolve('hello'), 2000);
});
hi.then(result => console.log(result));
As for promises baffling you, I would recommend (aside from more reading) just playing around a lot in your IDE with setTimeout. I know you're already using setTimeout in your mock, but strip it down further and just run the code in its own file, so that you control the whole environment. With lots of console.log('blah') to see the order and such. Also ensure you're just as familiar with callbacks, as promises are just syntactic sugar for callbacks. Try to read up on the JS event loop at the same time - it might provide some context if you know how and when a callback/promise executes.
Note that you can even add .then() after the callback has resolved or rejected. Hence the term "promise" - it's literally a promise that your .then() function will run. https://en.wikipedia.org/wiki/Promise_theory
const hi = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('one second');
resolve();
}, 1000);
});
setTimeout(() => {
hi.then(() => console.log('two seconds. this executes approximately a full second after the first promise has resolved'));
}, 2000);
This question already has answers here:
Asynchronous exception handling with bluebird promises
(3 answers)
Closed 4 years ago.
If there is a promise with an asynchronous function in it and if in the asynchronous function an error happens the promise doesn't catch but throws an error and crashes the application, which I don't understand.
Obviously I would like to handle the error, do you know why does the promise behave like this and what would be a way around it?
thanks
// this promise will have an error since param is not defined,
// and the promise won't be caught
function randomPromise(param) {
return new Promise((resolve, reject) => {
setTimeout(() => {
param[0] = 11;
}, 2000);
});
}
randomPromise()
.then(() => {
console.log('nothing');
})
.catch((e) => {
console.log('with set timeout or any async function in the promise, the error caused by \'param[0] = 11;\' wont bring the control here into the catch block just throws an error and crashes the application');
console.log(e);
});
// this promise will have an error since param is not defined
// but the promise will be caught
function randomPromiseGoesToCatchBlock(param) {
return new Promise((resolve, reject) => {
param[0] = 11;
});
}
randomPromiseGoesToCatchBlock()
.then(() => {
console.log('nothing');
})
.catch((e) => {
console.log('without the setTimeout function or any async function the error caused by \'param[0] = 11;\' brings the control here into the catch block');
console.log(e);
});
Errors that are thrown inside a Promise constructor and that occur asynchronously need to be explicitly try/catched so that reject can be called, so that the Promise control flow can be transferred to the Promise's catch. For example:
// this promise will have an error since param is not defined, and the promise wont be catched
function randomPromise(param) {
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
param[0] = 11;
} catch(e) {
reject(e);
}
}, 2000);
});
}
randomPromise()
.catch((e) => {
console.log(e.message);
});
Otherwise, neither resolve nor reject will be called, and the error was asynchronous, so the thread the Promise was created on has already ended, so the interpreter doesn't know that the thrown error should reject that Promise without you explicitly telling it to.
In contrast, errors thrown synchronously inside a Promise constructor will automatically result in the constructed Promise rejecting immediately.
I have created a long running Promise that I'm wrapping with this simple function that I created to create a watch a Promise race.
The function is below:
export const promiseTimeout = (
promise,
timeoutMs = 10000, //10 secs
message = 'Timeout reached, please try again',
) =>
Promise.race([
promise,
new Promise((resolve, reject) =>
setTimeout(() => {
reject(message);
}, timeoutMs),
),
]);
The way I'm planning to use it is that I would pass the long running Promise that might require other unpredictable resources such as internet, file, system setting, etc.
Usage would be like below:
const result = await promiseTimeout(longRunningFunction())
.catch(err => /* do something with the error , show toast or alert */);;
What is currently happening with this is that whenever the timeout is reached it would call the catch but the operation of the longRunningFunction will still continue.
How can I stop the operations on the passed Promise in argument if ever timeout is reached?
How can I stop the operations on the passed Promise in argument if ever timeout is reached?
Hey, sorry, we don't have cancellation of async functions yet.
Note however that a promise is a value and not an action, once you have the promise given we won't have cancellable promises in JavaScript it is impossible to cancel the action.
The only thing you can do is to do something like the cancellation proposal and write your longRunningFunction with a token:
function longRunningFunction() {
const signal = { requested: false };
async function internal() {
// your regular code here
// whenever you can stop execution:
if(signal.requested) {
return; // and cancel internal operations
}
}
let res = internal();
res.signal = signal;
return res;
}
Then write your race as:
export const promiseTimeout = (
promise,
timeoutMs = 10000, //10 secs
message = 'Timeout reached, please try again',
) =>
Promise.race([
promise,
new Promise((resolve, reject) =>
setTimeout(() => {
reject(message);
if (promise.signal) promise.signal.requested = true;
}, timeoutMs),
),
]);