Create a promise that timesout if callback never fired - javascript

I am curious how you are supposed to set a timeout for a function that returns a callback, if the callback is never fired, the promise will never resolve.
const mockCliAsync = (argv, stdio, timeout) => {
return new Promise((resolve, reject) => {
let timedout = false
const timeoutTimer = setTimeout(() => {
timedout = true
kill()
return reject(new Error('timeout'))
}, timeout)
const kill = mockCli(argv, stdio, (error, result) => {
if (!timedout) {
clearTimeout(timeoutTimer)
if (error) return reject(error)
return resolve(result)
}
})
})
}

Related

stop promises execution on certain time

function sleep(t) {
return new Promise((resolve, reject) =>{
setTimeout(() => {
console.log('timeout!')
return resolve({isTimeout: true})
}, t);
});
}
function thirdPartyFunction(t) { // thirdPartyFunction can't be edited
return new Promise((resolve, reject) =>{
setTimeout(() => {
console.log('thirdPartyFunction completed!')
return resolve({success: true})
}, t);
});
}
function main() {
return new Promise(async(resolve, reject) => {
try {
let thirdPartyFunctionExecutionTime =  Math.floor(Math.random() * 10) + 1;
thirdPartyFunction(thirdPartyFunctionExecutionTime * 1000, false).then( (r) => {
console.log('should not execute this if thirdPartyFunctionExecutionTime > timeout') // only goal
// other code which is not useful after timeout
});
const timeout = 3;
console.log(`thirdPartyFunctionExecutionTime: ${thirdPartyFunctionExecutionTime}, timeout - ${timeout}`)
await sleep(timeout * 1000, true);
throw 'stop main()'
// return
} catch (error) {
console.log('in catch')
return;
}
})
}
main()
Timeout is fixed. thirdPartyFunctionExecutionTime might be very large (sometimes) in my actual case, say 30 secs. I don't want something to be running on background after timeout.
thirdPartyFunction promise function should stop execution on timeout.
FYI, here's a generic function to add a timeout with the option of cancelling. This can be used with any promise to add a timeout to it.
If the underlying asynchronous operation represented by the promise is cancellable, then you can supply a cancel function. If the async operation finishes first, this will clean up the timer for you automatically so it doesn't keep running.
// the main purpose of this class is so that callers can test
// to see if the reject reason is a TimeoutError vs. some other type of error
class TimeoutError extends Error {
constructor(...args) {
super(...args);
}
}
// add a timeout to any promise
function addTimeout(promise, t, timeoutMsg = "timeout") {
let timer;
const timerPromise = new Promise((resolve, reject) => {
timer = setTimeout(() => {
timer = null;
// if the promise has a .cancel() method, then call it
if (typeof promise.cancel === "function") {
try {
promise.cancel();
} catch(e) {
console.log(e);
}
}
reject(new TimeoutError(timeoutMsg));
}, t)
});
// make sure the timer doesn't keep running if the promise finished first
promise.finally(() => {
if (timer) {
clearTimeout(timer);
}
})
return Promise.race([promise, timerPromise]);
}
And, you could then use this in your code like this:
function main() {
addTimeout(thirdPartyFunction(...), 3000).then(result => {
// thirdPartyFunction succeeded and timeout was not hit
// use the result here
}).catch(err => {
console.log(err);
// an error occurred here
if (err instanceof TimeoutError) {
// timeout
} else {
// other error
}
})
}
If your asynchronous operation has an ability to be cancelled if this operation times out, you could support that like this:
function main() {
let p = thirdPartyFunction(...);
p.cancel = function () {
// cancel the thirdPartyFunction here (depends uponn the specific operation)
}
addTimeout(p, 3000).then(result => {
// thirdPartyFunction succeeded and timeout was not hit
// use the result here
}).catch(err => {
console.log(err);
// an error occurred here
if (err instanceof TimeoutError) {
// timeout
} else {
// other error
}
})
}

how to call second API after first API result is retrieved

I have a API which called the bank end :
this.http.get(`http://localhost:44301/consentinitiation/${this.qid}`)
.pipe(retryWhen(_ => {
this.showIt=true
return interval(1000)
}))
.subscribe(result => {result
console.log(result);
this.qrcodelink=result["qrCodeLink"];
setTimeout(() => {
this.loadingSpinner=false;
}, 5000);
})
It has a result and has a status which is "Recieved" after that i should call the API again unstil i get the status "Finalized" and dont how to call the API again ,after the first call is finished,because if i write it below the first one i guess they will call the simultaneously,any idea?
the problem that u describe in description is called polling ( make request in interval until u got an expected result )
here is poll implementation in rxjs way
makeSomeRequestsToBank() {
this.http.get('https://').pipe(
switchMap(result => {
// if status is recieved, start polling
if (result.status === 'Recieved') {
return this.pollStatus();
}
if (result.status === 'Finalized') {
return of(result)
}
// else do some thing else, depends on u business logic
// keep in mind that switchMap should return an observable for futher chaining
return of(undefined);
}),
).subscribe(result => {
if (!result) return;
this.qrcodelink=result["qrCodeLink"];
setTimeout(() => {
this.loadingSpinner=false;
}, 5000);
}
pollStatus(): Observable<any> {
const POLLING_INTERVAL = 700; // poll in ms
const isSuccessFn = (response: string) => response === 'Finalized'; // the condition to stop polling and return result
return timer(0, POLLING_INTERVAL).pipe(
switchMap(() => this.http.get('https://')),
skipWhile(response => isSuccessFn(response)),
);
}
You can do it with using Promise.
ngOnInit(){
this.callFirstApi();
}
firstApi(): Promise<any> {
return new Promise((resolve, reject) => {
this.http.get(API_URL).subscribe((data) => {
resolve(data);
}, (error) => {
reject(error);
});
});
}
secApi(): Promise<any> {
return new Promise((resolve, reject) => {
this.http.get(API_URL).subscribe((data) => {
resolve(data);
}, (error) => {
reject(error);
});
});
}
callFirstApi(){
this.firstApi().then(response => {
this.callSecApi();
}).catch(err => {
})
}
callSecApi(){
this.secApi().then(response => {
}).catch(err => {
})
}

Reject all functions if one is rejected

I have a similar problem in dialogflow fulfillment where I need to book any appointments on the Google calendar. I would reject all functions p1, p2 and p3 if only one of them is rejected. In the code below, although p2 is rejected, the others p1 and p3 are executed (I wish all functions p1, p2 and p3 were not performed).
function f1() {
return Promise.all([p1(), p2(), p3()])
.then(value => {
alert('ok');
})
.catch(err => {
console.log('err: ' + err);
});
}
function p1() {
new Promise((resolve, reject) => {
setTimeout(resolve, 1000, alert("one"));
});
}
function p2() {
new Promise((resolve, reject) => {
reject('reject');
});
}
function p3() {
new Promise((resolve, reject) => {
setTimeout(resolve, 3000, alert("three"));
});
}
f1();
Well it contains a lot of code to implement it so I would give short instructions.
You need to have a way to reject your request or whatever you are doing. For examples with axious we can use CancelToken to cancel HTTP request.
Now you need to subscribe on each request and cancel the request or whatever you are using.
It is not clear what exactly you need. Functions can not be rejected or not executed if you run them in parallel. You can only notify the internal function code that the cancel operation was requested from the outside. We don't know what async operations are performed inside your async functions. Demo
import { CPromise } from "c-promise2";
function makePromise(ms, label) {
return new CPromise((resolve, reject, { onCancel }) => {
setTimeout(resolve, ms);
onCancel(() => console.log(`onCancel(${label})`));
});
}
CPromise.all([
makePromise(2000, "one"),
makePromise(1500, "two").then(() => {
throw Error("Oops");
}),
makePromise(4000, "three")
]).then(
(v) => console.log(`Done: ${v}`),
(e) => console.warn(`Fail: ${e}`)
);
onCancel(one)
onCancel(three)
Fail: Error: Oops
The problem is you're not returning anything from p1, p2, p3. So when you call Promise.all([p1(), p2(), p3()]) which is actually calling with Promise.all([undefined, undefined, undefined])(resolves anyway) which does not have a rejection. That's why you're not seeing the error.
Add return in your functions.
function f1() {
return Promise.all([p1(), p2(), p3()])
.then(value => {
alert('ok');
})
.catch(err => {
console.log('err: ' + err);
});
}
function p1() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
});
}
function p2() {
return new Promise((resolve, reject) => {
reject('reject');
});
}
function p3() {
return new Promise((resolve, reject) => {
setTimeout(resolve, 3000);
});
}
f1();
Remember Promises are not cancellable, if you really want cancel execution part, you can try something like this. I don't guarantee this works all the time and I don't think good idea to do in this way.
const timers = []
function f1() {
return Promise.all([p1(), p2(), p3()])
.then(value => {
alert('ok');
})
.catch(err => {
console.log('err: ' + err);
timers.forEach((timerId) => clearTimeout(timerId))
});
}
function p1() {
return new Promise((resolve, reject) => {
const timerId = setTimeout(() => {
alert(1)
resolve()
}, 1000);
timers.push(timerId)
});
}
function p2() {
return new Promise((resolve, reject) => {
reject('reject');
});
}
function p3() {
return new Promise((resolve, reject) => {
const timerId = setTimeout(() => {
alert(2)
resolve()
}, 3000);
timers.push(timerId)
});
}
f1();

Returning another aysnc function in resolve

I have a function call like so:
await someFunction(foo);
With someFunction defined like:
const someFunction = foo => {
return new Promise((resolve, reject) => {
if (foo) {
return resolve(someOtherPromise());
}
reject();
}
}
Is this valid code? I have tested it and it seems ok.
Is it correct that await someFunction() will transform to await someOtherPromise() if foo is true i.e. will it continue waiting for someOtherPromise to resolve?
Yes, it is valid code and works by resolving with a promise, but smells like the Promise constructor antipattern. I would instead recommend to write
function someFunction(foo) {
if (foo)
return someOtherPromise();
else
return Promise.reject();
}
or
async function someFunction(foo) {
if (foo)
return someOtherPromise();
else
throw;
}
(and better throw a new Error with a message, not undefined)
I've written some test code to see if it's true:
const someFunction = foo => {
return new Promise((resolve, reject) => {
if (foo) {
return resolve(someOtherPromise(2000));
}
reject();
});
}
const someOtherPromise = async(ms) => {
return new Promise(resolve => setTimeout(() => {
console.log("i'm about to resolve in someOtherPromise");
resolve();
}, ms));
}
const run = async () => {
await someFunction('foo');
console.log('finished waiting for someFunction');
}
run();
I have written 3 function to test it out.
c = () => new Promise((resolve, reject) => {
setTimeout(() => {
console.log('c');
resolve();
}, 1000);
});
b = () => new Promise((resolve, reject) => {
setTimeout(() => {
console.log('b');
resolve(c());
}, 1000);
});
a = async () => {
console.log('a before');
await b();
console.log('a after');
};
a();
As we can see, the output order is a before, b, c then a after. So await had waited for b and continued waiting for c.

Does promise resolved in n-th setTimeout cause memory leak?

I can see in Chrome task manager that the tab in which following code is running eats more and more memory, and it is not released until the promise is resolved
UPDATE
Main idea here is to use a single 'low level' method which would handle "busy" responses from the server. Other methods just pass url path with request data to it and awaiting for a valuable response.
Some anti-patterns was removed.
var counter = 1
// emulates post requests sent with ... axios
async function post (path, data) {
let response = (counter++ < 1000) ? { busy: true } : { balance: 3000 }
return Promise.resolve(response)
}
async function _call (path, data, resolve) {
let response = await post()
if (response.busy) {
setTimeout(() => {
_call(path, data, resolve)
}, 10)
throw new Error('busy')
}
resolve(response.balance)
}
async function makePayment (amount) {
return new Promise((resolve, reject) => {
_call('/payment/create', {amount}, resolve)
})
}
async function getBalance () {
return new Promise((resolve, reject) => {
_call('/balance', null, resolve)
})
}
makePayment(500)
.then(() => {
getBalance()
.then(balance => console.log('balance: ', balance))
.catch(e => console.error('some err: ', e))
})
The first time you call _call() in here:
async function getBalance () {
return new Promise((resolve, reject) => {
_call('/balance', null, resolve)
})
}
It will not call the resolve callback and it will return a rejected promise and thus the new Promise() you have in getBalance() will just do nothing initially. Remember, since _call is marked async, when you throw, that is caught and turned into a rejected promise.
When the timer fires, it will call resolve() and that will resolve the getBalance() promise, but it will not have a value and thus you don't get your balance. By the time you do eventually call resolve(response.balance), you've already called that resolve() function so the promise it belongs to is latched and won't change its value.
As others have said, there are all sorts of things wrong with this code (lots of anti-patterns). Here's a simplified version that works when I run it in node.js or in the snippet here in the answer:
function delay(t, val) {
return new Promise(resolve => {
setTimeout(resolve.bind(null, val), t);
});
}
var counter = 1;
function post() {
console.log(`counter = ${counter}`);
// modified counter value to 100 for demo purposes here
return (counter++ < 100) ? { busy: true } : { balance: 3000 };
}
function getBalance () {
async function _call() {
let response = post();
if (response.busy) {
// delay, then chain next call
await delay(10);
return _call();
} else {
return response.balance;
}
}
// start the whole process
return _call();
}
getBalance()
.then(balance => console.log('balance: ', balance))
.catch(e => console.error('some err: ', e))

Categories