Nodejs Timeout a Promise If Failed to Complete in Time

NodeJS Timeout a Promise if failed to complete in time

Native JavaScript promises don't have any timeout mechanism.

The question about your implementation would probably be a better fit for http://codereview.stackexchange.com, but a couple of notes:

  1. You don't provide a means of actually doing anything in the promise, and

  2. There's no need for clearTimeout within your setTimeout callback, since setTimeout schedules a one-off timer.

  3. Since a promise can't be resolved/rejected once it's been resolved/rejected, you don't need that check.

So continuing your myPromise function approach, perhaps something like this:

function myPromise(timeout, callback) {
return new Promise((resolve, reject) => {
// Set up the timeout
const timer = setTimeout(() => {
reject(new Error(`Promise timed out after ${timeout} ms`));
}, timeout);

// Set up the real work
callback(
(value) => {
clearTimeout(timer);
resolve(value);
},
(error) => {
clearTimeout(timer);
reject(error);
}
);
});
}

Used like this:

myPromise(2000, (resolve, reject) => {
// Real work is here
});

(Or possibly slightly less complicated, see under the divider below.)

I'd be slightly concerned about the fact that the semantics are different (no new, whereas you do use new with the Promise constructor). But the bigger issue with it is that it assumes you're always creating a promise from scratch, but you usually want to be able to use a promise you already have.

You can deal with both issues by subclassing Promise:

class MyPromise extends Promise {
constructor(timeout, callback) {
// We need to support being called with no milliseconds
// value, because the various Promise methods (`then` and
// such) correctly call the subclass constructor when
// building the new promises they return.
const haveTimeout = typeof timeout === "number";
const init = haveTimeout ? callback : timeout;
super((resolve, reject) => {
if (haveTimeout) {
const timer = setTimeout(() => {
reject(new Error(`Promise timed out after ${timeout}ms`));
}, timeout);
init(
(value) => {
clearTimeout(timer);
resolve(value);
},
(error) => {
clearTimeout(timer);
reject(error);
}
);
} else {
init(resolve, reject);
}
});
}
// Pick your own name of course. (You could even override `resolve` itself
// if you liked; just be sure to do the same arguments detection we do
// above in the constructor, since you need to support the standard use of
// `resolve`.)
static resolveWithTimeout(timeout, x) {
if (!x || typeof x.then !== "function") {
// `x` isn't a thenable, no need for the timeout,
// fulfill immediately
return this.resolve(x);
}
return new this(timeout, x.then.bind(x));
}
}

Usage (if constructing a new promise):

let p = new MyPromise(300, (resolve, reject) => {
// ...
});
p.then((value) => {
// ...
})
.catch((error) => {
// ...
});

Usage (if using a promise you already have):

MyPromise.resolveWithTimeout(100, somePromiseYouAlreadyHave)
.then((value) => {
// ...
})
.catch((error) => {
// ...
});

Live Example:

"use strict";

class MyPromise extends Promise {
constructor(timeout, callback) {
// We need to support being called with no milliseconds
// value, because the various Promise methods (`then` and
// such) correctly call the subclass constructor when
// building the new promises they return.
const haveTimeout = typeof timeout === "number";
const init = haveTimeout ? callback : timeout;
super((resolve, reject) => {
if (haveTimeout) {
const timer = setTimeout(() => {
reject(new Error(`Promise timed out after ${timeout}ms`));
}, timeout);
init(
(value) => {
clearTimeout(timer);
resolve(value);
},
(error) => {
clearTimeout(timer);
reject(error);
}
);
} else {
init(resolve, reject);
}
});
}
// Pick your own name of course. (You could even override `resolve` itself
// if you liked; just be sure to do the same arguments detection we do
// above in the constructor, since you need to support the standard use of
// `resolve`.)
static resolveWithTimeout(timeout, x) {
if (!x || typeof x.then !== "function") {
// `x` isn't a thenable, no need for the timeout,
// fulfill immediately
return this.resolve(x);
}
return new this(timeout, x.then.bind(x));
}
}

// Some functions for the demonstration
const neverSettle = () => new Promise(() => {});
const fulfillAfterDelay = (delay, value) => new Promise((resolve) => setTimeout(resolve, delay, value));
const rejectAfterDelay = (delay, error) => new Promise((resolve, reject) => setTimeout(reject, delay, error));

const examples = [
function usageWhenCreatingNewPromise1() {
console.log("Showing timeout when creating new promise");
const p = new MyPromise(100, (resolve, reject) => {
// We never resolve/reject, so we test the timeout
});
return p.then((value) => {
console.log(`Fulfilled: ${value}`);
})
.catch((error) => {
console.log(`Rejected: ${error}`);
});
},

function usageWhenCreatingNewPromise2() {
console.log("Showing when the promise is fulfilled before the timeout");
const p = new MyPromise(100, (resolve, reject) => {
setTimeout(resolve, 50, "worked");
});
return p.then((value) => {
console.log(`Fulfilled: ${value}`);
})
.catch((error) => {
console.log(`Rejected: ${error}`);
});
},

function usageWhenCreatingNewPromise3() {
console.log("Showing when the promise is rejected before the timeout");
const p = new MyPromise(100, (resolve, reject) => {
setTimeout(reject, 50, new Error("failed"));
});
return p.then((value) => {
console.log(`Fulfilled: ${value}`);
})
.catch((error) => {
console.log(`Rejected: ${error}`);
});
},

function usageWhenYouAlreadyHaveAPromise1() {
console.log("Showing timeout when using a promise we already have");
return MyPromise.resolveWithTimeout(100, neverSettle())
.then((value) => {
console.log(`Fulfilled: ${value}`);
})
.catch((error) => {
console.log(`Rejected: ${error}`);
});
},

function usageWhenYouAlreadyHaveAPromise2() {
console.log("Showing fulfillment when using a promise we already have");
return MyPromise.resolveWithTimeout(100, fulfillAfterDelay(50, "worked"))
.then((value) => {
console.log(`Fulfilled: ${value}`);
})
.catch((error) => {
console.log(`Rejected: ${error}`);
});
},

function usageWhenYouAlreadyHaveAPromise3() {
console.log("Showing rejection when using a promise we already have");
return MyPromise.resolveWithTimeout(100, rejectAfterDelay(50, new Error("failed")))
.then((value) => {
console.log(`Fulfilled: ${value}`);
})
.catch((error) => {
console.log(`Rejected: ${error}`);
});
},

async function usageInAnAsyncFunction1() {
console.log("Showing timeout in async function");
try {
const value = await MyPromise.resolveWithTimeout(100, neverSettle());
console.log(`Fulfilled: ${value}`);
} catch (error) {
console.log(`Rejected: ${error}`);
}
},

async function usageInAnAsyncFunction2() {
console.log("Showing fulfillment in async function");
try {
const value = await MyPromise.resolveWithTimeout(100, fulfillAfterDelay(50, "worked"));
console.log(`Fulfilled: ${value}`);
} catch (error) {
console.log(`Rejected: ${error}`);
}
},

async function usageInAnAsyncFunction3() {
console.log("Showing rejection in async function");
try {
const value = await MyPromise.resolveWithTimeout(100, rejectAfterDelay(50, new Error("failed")));
console.log(`Fulfilled: ${value}`);
} catch (error) {
console.log(`Rejected: ${error}`);
}
},
];

(async () => {
for (const example of examples) {
try {
await example();
} catch (e) {
}
}
})();
/* Shows the cosole full height in the snippet */
.as-console-wrapper {
max-height: 100% !important;
}

How to add a timeout in a promise?

You can wrap setTimeout in a Promise and create a little "wait"-function which you can then use with Promise.race:

function wait(ms) {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error('timeout succeeded')), ms);
});
}

try {
const result = await Promise.race([wait(1000), yourAsyncFunction()]);
} catch(err) {
console.log(err);
}

With this code Promise.race will reject if yourAsyncFunction takes longer than 1000 ms to resolve/reject, otherwise result will yield the resolved value from yourAsyncFunction.

Avoid uncaught exception when a promise gets rejected due to timeout?

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()

How to cancel timeout inside of Javascript Promise?

Edit 2021 all platforms have converged on AbortController as the cancellation primitive and there is some built in support for this.

In Node.js

// import { setTimeout } from 'timers/promises' // in ESM
const { setTimeout } = require('timers/promises');
const ac = new AbortController();

// cancellable timeout
(async () => {
await setTimeout(1000, null, { signal: ac.signal });
})();

// abort the timeout, rejects with an ERR_ABORT
ac.abort();

In Browsers

You can polyfill this API and use the same as the example above:


function delay(ms, value, { signal } = {}) {
return new Promise((resolve, reject) => {
const listener = () => {
clearTimeout(timer);
reject(signal.reason);
};
signal?.throwIfAborted();
const timer = setTimeout(() => {
signal?.removeEventListener('abort', listener);
resolve(value);
}, ms);
signal?.addEventListener('abort', listener);
});
}

What you can do it that, you can return a canceller from your timeout function and invoke it when needed. This way you do not need to store the timeoutid globally (or on the outer scope) and also this can manage multiple calls to the function as well. Each instance of the object return by the function timeout will have its own canceler that can perform the cancellation.

function timeout(ms) {
var timeout, promise;

promise = new Promise(function(resolve, reject) {
timeout = setTimeout(function() {
resolve('timeout done');
}, ms);
});

return {
promise:promise,
cancel:function(){clearTimeout(timeout );} //return a canceller as well
};
}

var timeOutObj =timeout(3000);

timeOutObj.promise.then(function(result) {
console.log(result); // timeout done
});

//Cancel it.
timeOutObj.cancel();

Plnkr

node is waiting for a timeout that already killed

It looks like this code is something you're trying to fit into getLayerKeycodes() from this other question to somehow know when all the data has been received from your keyboard hardware.

I'll illustrate how you can plug into that without using timers. Here's what you started with in that other question:

Your original function

const getLayerKeycodes = (keyboard, layer, rows, columns) => {
//array that stores all the keycodes according to their order
let layerKeycodes = [];
//rows and columns start the count at 0 in low level, so we need to decrease one from the actual number.
columns --;
rows --;
//loop that asks about all the keycodes in a layer
const dataReceived = (err, data) => {
if(err) {
return err;
}
// push the current keycode to the array
// The keycode is always returned as the fifth object.
layerKeycodes.push(data[5]);
console.log(layerKeycodes);
};
for (let r = 0 , c = 0;c <= columns; r ++){
//callback to fire once data is receieved back from the keyboard.
if(r > rows){
c++;
//r will turn to 0 once the continue fires and the loop executes again
r = -1;
continue;
}
//Start listening and call dataReceived when data is received
keyboard[0].read(dataReceived);
//Ask keyboard for information about keycode
// [always 0 (first byte is ignored),always 0x04 (get_keycode),layer requested,row being checked,column being checked]
keyboard[0].write([0x01,0x04,layer,r,c]);
}
console.log(layerKeycodes);
}

Manually created promise to resolve upon completion of all rows/columns

And, you can incorporate the completion detection code inside of the dataReceived() function without any timers and without reworking much of the rest of your logic like this:

const getLayerKeycodes = (keyboard, layer, rows, columns) => {
return new Promise((resolve, reject) => {
//array that stores all the keycodes according to their order
const layerKeycodes = [];
const totalCells = rows * columns;
let abort = false;

//rows and columns start the count at 0 in low level, so we need to decrease one from the actual number.
columns--;
rows--;

// function that gets with keyboard data
function dataReceived(err, data) => {
if (err) {
abort = true; // set flag to stop sending more requests
reject(err);
return;
}
// push the current keycode to the array
// The keycode is always returned as the fifth object.
layerKeycodes.push(data[5]);

// now see if we're done with all of them
if (layerKeycodes.length >= totalCells) {
resolve(layerKeycodes);
}
}

// loop that asks about all the keycodes in a layer
for (let r = 0, c = 0; c <= columns; r++) {
// stop sending more requests if we've already gotten an error
if (abort) {
break;
}
//callback to fire once data is receieved back from the keyboard.
if (r > rows) {
c++;
//r will turn to 0 once the continue fires and the loop executes again
r = -1;
continue;
}
//Start listening and call dataReceived when data is received
keyboard[0].read(dataReceived);
//Ask keyboard for information about keycode
// [always 0 (first byte is ignored),always 0x04 (get_keycode),layer requested,row being checked,column being checked]
keyboard[0].write([0x01, 0x04, layer, r, c]);
}
}
}
}

A simplified version by promisifying the read function

And, here's a bit simpler version that promisifies the read function so we can use await on it and then just use an async function and a dual nested for loop for simpler loop mechanics.

const util = require('util');

async function getLayerKeycodes(keyboard, layer, rows, columns) => {
// promisify the keyboard.read()
const readKeyboard = util.promisify(keyboard[0].read).bind(keyboard[0]);

//array that stores all the keycodes according to their order
const layerKeycodes = [];

// loop that asks about all the keycodes in a layer
for (let rowCntr = 0; rowCntr < rows; rowCntr++) {
for (let colCntr = 0; colCntr < columns; colCntr++) {

// Start listening and collect the promise
const readPromise = readKeyboard();
// Ask keyboard for information about keycode
// [always 0 (first byte is ignored),always 0x04 (get_keycode),layer requested,row being checked,column being checked]
keyboard[0].write([0x01, 0x04, layer, rowCntr, colCntr]);

// wait for data to come in
const data = await readPromise;

// push the current keycode to the array
// The keycode is always returned as the fifth object.
layerKeycodes.push(data[5]);
}
}
return layerCodes;
}

This also makes sure that we send a write, then wait for the data from that write to come back before we sent the next write which seems like a potentially safer way to handle the hardware. Your original code fires all the writes at once which might work, but it seems like the reads could come back in any order. This guarantees sequential order in the layerCodes array which seems safer (I'm not sure if that matters with this data or not).

Error handling in this last version is somewhat automatically handled by the async function and the promises. If the read returns an error, then the readPromise will automatically reject which will abort our loop and in turn reject the promise that the async function returned. So, we don't have to do the abort checking that the previous function with the manually created promise had.


Now, of course, I don't have the ability to run any of these to test them so it's possible there are some typos somewhere, but hopefully you can work through any of those and see the concept for how these work.

What is the best general practice to timeout a function in promise

It depends on what you mean by timeout.

If you expect the function to stop, then no.

If you just want to stop waiting for it, then yes (quick to whip up in ES6):

var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
var timeout = (p, ms) => Promise.race([p, wait(ms).then(() => {
throw new Error("Timeout after " + ms + " ms");
})]);

var wait = ms => new Promise(resolve => setTimeout(resolve, ms));var timeout = (p, ms) => Promise.race([p, wait(ms).then(() => {  throw new Error("Timeout after " + ms + " ms");})]);
// Example:
var log = msg => div.innerHTML += "<p>" + msg + "</p>";var failed = e => log(e.toString() + ", line " + e.lineNumber);
log("Waiting 5 seconds...");timeout(wait(5000), 2000).then(() => log("...Done.")).catch(failed);
<div id="div"></div>

How to add timeout to this async function

You could define a wait-function that rejects after the given amount of time and then use Promise.race on that wait-function and your Promise.all. Now, if your promises inside Promise.all take longer than the wait, Promise.race will reject, otherwise the resolved values will be assigned to results.

function wait(ms) {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error("wait time exceeded"));
}, ms);
})
}

(async () => {
const tasks = [];
for (let i = 1; i <= NB_PARALLEL; i++) {
tasks.push(buildReferenceSpaceCollection(json));
}
// 5 mins in ms
const wait5MinPromise = wait(5*60*1000);
const results = await Promise.race([wait5MinPromise, Promise.all(tasks)]);
console.log(results);
process.exit(0);
})().catch(console.error)

;



Related Topics



Leave a reply



Submit