Promise: chained timeout - javascript

The Promise API doesn't have a chainable timeout option, but in Steve Sanderson's presentation at NDC Conference (at 15:31 here https://www.youtube.com/watch?v=9G8HEDI3K6s&feature=youtu.be&t=15m31s), he presented an elegant chainable timeout on the fetch API. It looks like this:
The great thing about this approach is that the resolve handler still completed (e.g. the response was still put into cache) even after the timeout. He demo'd this during his presentation (and available at the YouTube link above). Anyone know how this chainable timeout was implemented?

I usually use Promise.race to implement timeouts. Normally I haven't tried to make this chainable, but that's a cool idea so I'll give it a go in a moment.
This is how I'd normally use it to implement a timeout:
function timeout (promise, duration) {
return Promise.race([
promise,
new Promise((resolve, reject) => {
setTimeout(
() => reject(new Error("Timeout")),
duration
)
})
]);
}
timeout(fetch("something"), 5000)
.then(() => {
// ... Operation completed without timeout ...
})
.catch(err => {
// ... An error occurred possibly a timeout ...
));
You might be able to make this chainable by attaching your function to the prototype of the Promise class (maybe depending on which promise library you are actually using):
Promise.prototype.timeout = function (duration) {
return Promise.race([
this,
new Promise((resolve, reject) => {
setTimeout(
() => reject(new Error("Timeout")),
duration
)
})
]);
};
fetch("something")
.then(() => ...) // This executes before the timeout.
.timeout(5000)
.catch(err => ...);

Related

Chaining/Nesting promises

I'm having nightmare trying to chain aws js sdk. Currently I have the following (omitted any parameters to remove chaff, the calls work in isolation so its safe to assume they are correct)
function deploy() {
return sts.assumeRole().promise()
.then(() => {
return new Promise((resolve, reject) => {
return new Promise((resolve, reject) => {
AWS.config.update({
credentials: {
accessKeyId: data.Credentials.AccessKeyId,
secretAccessKey: data.Credentials.SecretAccessKey,
sessionToken: data.Credentials.SessionToken
}
});
resolve();
})
.then(() => {
fsPromise.readFile(packageLocation).then(() => {
return s3.upload().promise();
});
});
}).then(() => {
return ebs.createApplicationVersion().promise();
}).then(() => {
return ebs.createEnvironment().promise();
});
};
If I run each then one by one in order it works, however when i run them together the createApplicationVersion call fails as the upload hasn't worked due to the fact it is attempting to upload before assumeRole has finished. I assume...
I am obviously doing something wrong, but It isn't clear to me what.
The use of promises there is much more complicated than necessary. Remember that then (and catch and finally) return new promises that are settled based on what their handlers return. new Promise within a then (or catch or finally) handler is almost never necessary; just return whatever you would resolve that promise with (a value or another promise), and the promise returned by then et. al. will be resolved to it.
That chain should look like this, if I assume AWS.config.update() is synchronous:
function deploy() {
return sts.assumeRole().promise()
.then(() => {
AWS.config.update();
})
.then(() => fsPromise.readFile(packageLocation))
.then(() => s3.upload().promise())
.then(() => ebs.createApplicationVersion().promise())
.then(() => ebs.createEnvironment().promise());
}
Each step in that chain will wait for the previous step to complete.
If you don't want deploy to fulfill its promise with the fulfillment value of ebs.createEnvironment().promise(), then add a final then handler:
.then(() => ebs.createEnvironment().promise())
.then(() => { });
}
(I'll assume you want to do that for the following examples.)
Or like this if AWS.config.update() is asynchronous and returns a promise:
function deploy() {
return sts.assumeRole().promise()
.then(() => AWS.config.update())
.then(() => fsPromise.readFile(packageLocation))
.then(() => s3.upload().promise())
.then(() => ebs.createApplicationVersion().promise())
.then(() => ebs.createEnvironment().promise())
.then(() => { });
}
Or, of course, in any relatively recent version of Node.js (I'm guessing from fsPromise and just general context that this is Node.js), you could use an async function:
async function deploy() {
await sts.assumeRole().promise();
await AWS.config.update(); // Or without `await` if it is synchronous and doesn't return a promise
await fsPromise.readFile(packageLocation);
await s3.upload().promise();
await ebs.createApplicationVersion().promise();
await ebs.createEnvironment().promise();
}
Side note about this code, and code like it you posted in a comment:
return new Promise((resolve, reject) => {
AWS.config.update({
credentials: {
accessKeyId: data.Credentials.AccessKeyId,
secretAccessKey: data.Credentials.SecretAccessKey,
sessionToken: data.Credentials.SessionToken
}
});
resolve();
})
There's no reason to use new Promise there. It doesn't convert the call to AWS.config.update into an asynchronous call or anything like that. If for some reason you needed a promise there (you don't in this case, AKAICT), you'd use Promise.resolve:
AWS.config.update(/*...*/);
return Promise.resolve();
But again, no need for that here. Remember that promises only provide a way to observe the result of an asynchronous process, they don't make a process asynchronous. (The only thing that they actually make asynchronous is observing the result.)
You don't need to nest your promises the way that you did. Taking a stab in the dark, try this:
function deploy() {
return sts.assumeRole().promise()
.then(() => { AWS.config.update() })
.then(() => fsPromise.readFile(packageLocation))
.then(() => s3.upload().promise())
.then(() => ebs.createApplicationVersion().promise())
.then(() => ebs.createEnvironment().promise())
};

ClearTimeout usage best practice within a promise

I have a question about clearTimeout method (sorry in advance for noob question). I am wondering where is the best place to clearTimeout in this following code? I have a "getResponse()"function which will be called multiple times. I am not sure where would be the best place to put the clearTimeout so that it will clear the timeout as soon as responseTimeout either resolves or rejects.Thanks
function getResponse() {
const responseTimeout = new Promise((resolve, reject) => {
let id = setTimeout(() => {
if (!messageHandled) {
reject(`Timed out to get response`);
}
}, 3000);
});
const responsePromise = new Promise((resolve, reject) => {
// some code which returns response promise
});
return Promise.race([
responsePromise,
responseTimeout
]);
}
Typically, I'll do it in the promise itself and avoid the overhead of two separate promises.
function getResponse(...params) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("Timed out to get response"))
// some code to abort request
}, 3000)
// some code which resolves promise
})
}
Usefully, promises ignore later resolutions and/or rejections after you resolve it the first time. If you can programmatically abort it without firing the resolution logic, this becomes much easier to do, but even if you can't, you can always slap a boolean to block future requests along the way. It also makes it easier to add support for canceling later on, which is also helpful. If you need to also shut down the timeout (in case you're doing some DOM mutation or file reading - you rarely need to do this), you could do something like this:
function getResponse(...params) {
return new Promise((reallyResolve, reallyReject) => {
let resolved = false
let id = setTimeout(() => {
reject(new Error("Timed out to get response"))
abort()
}, 3000)
// I'll use these instead of the `resolve`/`reject` callbacks directly.
function resolve(value) {
if (resolved) return
resolved = true
clearTimeout(id)
reallyResolve(value)
}
function reject(value) {
if (resolved) return
resolved = true
clearTimeout(id)
reallyReject(value)
}
function abort() {
// some code to abort request
}
// some code which resolves promise
})
}
This makes it so I don't have to worry about orchestrating everything, but it still works.
If you don't need abort logic (you only really need it for network requests and similar), this becomes a lot simpler:
function timeout(ms) {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error("Timed out")), ms)
})
}
function getResponse(...params) {
return Promise.race([
timeout(3000),
doSomethingAsync(),
])
}

Time based promise logic - implementing timeout

Let a promise p implementing a time-based logic similar to an HTTP request, ie. if it is pending for less than 5 seconds and a result has been acquired then resolve otherwise reject with a timeout. I want to use it as a wrapper in an event, so whether anything comes in or not, the promise should be settled anyway. Although the following snippet seems to work, I am not sure that it is implemented correctly:
let p = new Promise(function(resolve, reject) {
evt.on('dataStream', stream => {
if(stream.payload) {
let payload = stream.payload;
resolve(stream)
}
})
setTimeout( () => {
reject('Timeout!')
}, 5000)
});
p.then(res => console.log(res))
.catch(err => console.log(err))
Although the following snippet seems to work, I am not sure that it is implemented correctly:
I agree your snippet is right.
In addition, if your event is promise, I've found a similar way.
// Rough implementation. Untested.
function timeout(ms, promise) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject(new Error("timeout"))
}, ms)
promise.then(resolve, reject)
})
}
timeout(1000, event).then(function(stream) {
// process stream
}).catch(function(error) {
// might be a timeout error
})
From Fetch API request timeout?

What are some typical use-cases for `Promise.race()`? [duplicate]

As far as I know, there are two options about promise:
promise.all()
promise.race()
Ok, I know what promise.all() does. It runs promises in parallel, and .then gives you the values if both resolved successfully. Here is an example:
Promise.all([
$.ajax({ url: 'test1.php' }),
$.ajax({ url: 'test2.php' })
])
.then(([res1, res2]) => {
// Both requests resolved
})
.catch(error => {
// Something went wrong
});
But I don't understand what does promise.race() is supposed to do exactly? In other word, what's the difference with not using it? Assume this:
$.ajax({
url: 'test1.php',
async: true,
success: function (data) {
// This request resolved
}
});
$.ajax({
url: 'test2.php',
async: true,
success: function (data) {
// This request resolved
}
});
See? I haven't used promise.race() and it behaves like promise.race(). Anyway, is there any simple and clean example to show me when exactly should I use promise.race() ?
As you see, the race() will return the promise instance which is firstly resolved or rejected:
var p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, 'one');
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'two');
});
Promise.race([p1, p2]).then(function(value) {
console.log(value); // "two"
// Both resolve, but p2 is faster
});
For a scenes to be used, maybe you want to limit the cost time of a request :
var p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
])
p.then(response => console.log(response))
p.catch(error => console.log(error))
With the race() you just need to get the returned promise, you needn't care about which one of the promises in the race([]) firstly returned,
However, without the race, just like your example, you need to care about which one will firstly returned, and called the callback in the both success callback.
I've used it for request batching. We had to batch tens of thousands of records into batches for a long running execution. We could do it in parallel, but didn't want the number of pending requests to get out of hand.
Race lets us keep a fixed number of parallel promises running and add one to replace whenever one completes
const _ = require('lodash')
async function batchRequests(options) {
let query = { offset: 0, limit: options.limit };
do {
batch = await model.findAll(query);
query.offset += options.limit;
if (batch.length) {
const promise = doLongRequestForBatch(batch).then(() => {
// Once complete, pop this promise from our array
// so that we know we can add another batch in its place
_.remove(promises, p => p === promise);
});
promises.push(promise);
// Once we hit our concurrency limit, wait for at least one promise to
// resolve before continuing to batch off requests
if (promises.length >= options.concurrentBatches) {
await Promise.race(promises);
}
}
} while (batch.length);
// Wait for remaining batches to finish
return Promise.all(promises);
}
batchRequests({ limit: 100, concurrentBatches: 5 });
It's a piece to build a timeout system, where:
the request/computation may be canceled by another channel
it will still be used later, but we need an interaction now.
For an example of the second, one might show a spinner "instantly" while still defaulting to show real content if it comes in fast enough. Try running the below a few times - note at least some console message comes "instantly". This might normally be attached to perform operations on a UI.
The key to note is - the result of Promise.race is much less important than the side effects (though, this then is a code smell).
// 300 ms _feels_ "instant", and flickers are bad
function getUserInfo(user) {
return new Promise((resolve, reject) => {
// had it at 1500 to be more true-to-life, but 900 is better for testing
setTimeout(() => resolve("user data!"), Math.floor(900*Math.random()));
});
}
function showUserInfo(user) {
return getUserInfo().then(info => {
console.log("user info:", info);
return true;
});
}
function showSpinner() {
console.log("please wait...")
}
function timeout(delay, result) {
return new Promise(resolve => {
setTimeout(() => resolve(result), delay);
});
}
Promise.race([showUserInfo(), timeout(300)]).then(displayed => {
if (!displayed) showSpinner();
});
Inspiration credit to a comment by captainkovalsky.
An example of the first:
function timeout(delay) {
let cancel;
const wait = new Promise(resolve => {
const timer = setTimeout(() => resolve(false), delay);
cancel = () => {
clearTimeout(timer);
resolve(true);
};
});
wait.cancel = cancel;
return wait;
}
function doWork() {
const workFactor = Math.floor(600*Math.random());
const work = timeout(workFactor);
const result = work.then(canceled => {
if (canceled)
console.log('Work canceled');
else
console.log('Work done in', workFactor, 'ms');
return !canceled;
});
result.cancel = work.cancel;
return result;
}
function attemptWork() {
const work = doWork();
return Promise.race([work, timeout(300)])
.then(done => {
if (!done)
work.cancel();
return (done ? 'Work complete!' : 'I gave up');
});
}
attemptWork().then(console.log);
You can see from this one that the timeout's console.log is never executed when the timeout hits first. It should fail/succeed about half/half, for testing convenience.
Here's an easy example to understand the use of promise.race():
Imagine you need to fetch some data from a server and if the data takes too long to load (say 15 seconds) you want to show an error.
You would call promise.race() with two promises, the first being your ajax request and the second being a simple setTimeout(() => resolve("ERROR"), 15000)
Summary:
Promise.race is a JS built in function that accepts an iterable of Promises (e.g. Array) as an argument. This function then asynchronously returns a Promise as soon as one in of the Promises passed in the iterable is either resolved or rejected.
Example 1:
var promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('Promise-one'), 500);
});
var promise2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('Promise-two'), 100);
});
Promise.race([promise1, promise2]).then((value) => {
console.log(value);
// Both resolve, but promise2 is faster than promise 1
});
In this example first an array of Promises is passed in Promise.race. Both of the promises resolve but promise1 resolves faster. Therefore the promise is resolved with the value of promise1, which is the string 'Promise-one'.
Example 2:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('succes'), 2000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => reject('err'), 1000);
});
Promise.race([promise1, promise2])
.then((value) => {
console.log(value);
}).catch((value) => {
console.log('error: ' + value);
});
In this second example the second promise rejects faster than the first promise can resolve. Therefore Promise.race will return a rejected promise with the value of 'err' which was the value that Promise2 rejected with.
The key point to understand is that Promice.race takes an iterable of Promises and returns a Promise based on the first resolved or rejected promise in that iterable (with the corresponding resolve() or reject() values).
Let's take an sample workaround of Promise.race like below.
const race = (promises) => {
return new Promise((resolve, reject) => {
return promises.forEach(f => f.then(resolve).catch(reject));
})
};
You can see race function executes all promises, but whomever finishes first will resolve/reject with wrapper Promise.

Understanding promise.race() usage

As far as I know, there are two options about promise:
promise.all()
promise.race()
Ok, I know what promise.all() does. It runs promises in parallel, and .then gives you the values if both resolved successfully. Here is an example:
Promise.all([
$.ajax({ url: 'test1.php' }),
$.ajax({ url: 'test2.php' })
])
.then(([res1, res2]) => {
// Both requests resolved
})
.catch(error => {
// Something went wrong
});
But I don't understand what does promise.race() is supposed to do exactly? In other word, what's the difference with not using it? Assume this:
$.ajax({
url: 'test1.php',
async: true,
success: function (data) {
// This request resolved
}
});
$.ajax({
url: 'test2.php',
async: true,
success: function (data) {
// This request resolved
}
});
See? I haven't used promise.race() and it behaves like promise.race(). Anyway, is there any simple and clean example to show me when exactly should I use promise.race() ?
As you see, the race() will return the promise instance which is firstly resolved or rejected:
var p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, 'one');
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'two');
});
Promise.race([p1, p2]).then(function(value) {
console.log(value); // "two"
// Both resolve, but p2 is faster
});
For a scenes to be used, maybe you want to limit the cost time of a request :
var p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
])
p.then(response => console.log(response))
p.catch(error => console.log(error))
With the race() you just need to get the returned promise, you needn't care about which one of the promises in the race([]) firstly returned,
However, without the race, just like your example, you need to care about which one will firstly returned, and called the callback in the both success callback.
I've used it for request batching. We had to batch tens of thousands of records into batches for a long running execution. We could do it in parallel, but didn't want the number of pending requests to get out of hand.
Race lets us keep a fixed number of parallel promises running and add one to replace whenever one completes
const _ = require('lodash')
async function batchRequests(options) {
let query = { offset: 0, limit: options.limit };
do {
batch = await model.findAll(query);
query.offset += options.limit;
if (batch.length) {
const promise = doLongRequestForBatch(batch).then(() => {
// Once complete, pop this promise from our array
// so that we know we can add another batch in its place
_.remove(promises, p => p === promise);
});
promises.push(promise);
// Once we hit our concurrency limit, wait for at least one promise to
// resolve before continuing to batch off requests
if (promises.length >= options.concurrentBatches) {
await Promise.race(promises);
}
}
} while (batch.length);
// Wait for remaining batches to finish
return Promise.all(promises);
}
batchRequests({ limit: 100, concurrentBatches: 5 });
It's a piece to build a timeout system, where:
the request/computation may be canceled by another channel
it will still be used later, but we need an interaction now.
For an example of the second, one might show a spinner "instantly" while still defaulting to show real content if it comes in fast enough. Try running the below a few times - note at least some console message comes "instantly". This might normally be attached to perform operations on a UI.
The key to note is - the result of Promise.race is much less important than the side effects (though, this then is a code smell).
// 300 ms _feels_ "instant", and flickers are bad
function getUserInfo(user) {
return new Promise((resolve, reject) => {
// had it at 1500 to be more true-to-life, but 900 is better for testing
setTimeout(() => resolve("user data!"), Math.floor(900*Math.random()));
});
}
function showUserInfo(user) {
return getUserInfo().then(info => {
console.log("user info:", info);
return true;
});
}
function showSpinner() {
console.log("please wait...")
}
function timeout(delay, result) {
return new Promise(resolve => {
setTimeout(() => resolve(result), delay);
});
}
Promise.race([showUserInfo(), timeout(300)]).then(displayed => {
if (!displayed) showSpinner();
});
Inspiration credit to a comment by captainkovalsky.
An example of the first:
function timeout(delay) {
let cancel;
const wait = new Promise(resolve => {
const timer = setTimeout(() => resolve(false), delay);
cancel = () => {
clearTimeout(timer);
resolve(true);
};
});
wait.cancel = cancel;
return wait;
}
function doWork() {
const workFactor = Math.floor(600*Math.random());
const work = timeout(workFactor);
const result = work.then(canceled => {
if (canceled)
console.log('Work canceled');
else
console.log('Work done in', workFactor, 'ms');
return !canceled;
});
result.cancel = work.cancel;
return result;
}
function attemptWork() {
const work = doWork();
return Promise.race([work, timeout(300)])
.then(done => {
if (!done)
work.cancel();
return (done ? 'Work complete!' : 'I gave up');
});
}
attemptWork().then(console.log);
You can see from this one that the timeout's console.log is never executed when the timeout hits first. It should fail/succeed about half/half, for testing convenience.
Here's an easy example to understand the use of promise.race():
Imagine you need to fetch some data from a server and if the data takes too long to load (say 15 seconds) you want to show an error.
You would call promise.race() with two promises, the first being your ajax request and the second being a simple setTimeout(() => resolve("ERROR"), 15000)
Summary:
Promise.race is a JS built in function that accepts an iterable of Promises (e.g. Array) as an argument. This function then asynchronously returns a Promise as soon as one in of the Promises passed in the iterable is either resolved or rejected.
Example 1:
var promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('Promise-one'), 500);
});
var promise2 = new Promise((resolve, reject) => {
setTimeout(() => resolve('Promise-two'), 100);
});
Promise.race([promise1, promise2]).then((value) => {
console.log(value);
// Both resolve, but promise2 is faster than promise 1
});
In this example first an array of Promises is passed in Promise.race. Both of the promises resolve but promise1 resolves faster. Therefore the promise is resolved with the value of promise1, which is the string 'Promise-one'.
Example 2:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve('succes'), 2000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => reject('err'), 1000);
});
Promise.race([promise1, promise2])
.then((value) => {
console.log(value);
}).catch((value) => {
console.log('error: ' + value);
});
In this second example the second promise rejects faster than the first promise can resolve. Therefore Promise.race will return a rejected promise with the value of 'err' which was the value that Promise2 rejected with.
The key point to understand is that Promice.race takes an iterable of Promises and returns a Promise based on the first resolved or rejected promise in that iterable (with the corresponding resolve() or reject() values).
Let's take an sample workaround of Promise.race like below.
const race = (promises) => {
return new Promise((resolve, reject) => {
return promises.forEach(f => f.then(resolve).catch(reject));
})
};
You can see race function executes all promises, but whomever finishes first will resolve/reject with wrapper Promise.

Categories

Resources