Chase Chase - 4 months ago 23
Javascript Question

Using a promise to implement a timeout - throw an error

I'm trying to implement a timeout for an asynchronous function using a promise and setTimeout. I want to run an asynchronous operation, and if it doesn't complete by a certain amount of time, throw an error. Currently, this is what I have: (some of it found on stackoverflow)

function someOperation(cb) {
setTimeout(function() {
cb(null, 'Done');
}, 2000);
}


var p = new Promise(function(resolve, reject) {
setTimeout(function() {
reject(new Error('Operation timed out'));
}, 100);

someOperation(function(err, res) {
if (err) reject(err);
else resolve(res);
});

});

p.then(function(res) {
console.log(res);
}, function(err) {
console.log("Throwing error...")
throw err;
});


But the program doesn't stop when the error is thrown. Would anybody be able to tell me why, and if there is an easier way to do this? I'd be very much appreciative.

EDIT: Now trying to use bluebird for the first time and it is giving someOperationPromised.timeout is not a function. Am I doing this correctly?

var Promise = require("bluebird");

function someOperation(cb) {
setTimeout(function() {
cb('', 'Done');
}, 2000);
}

var someOperationPromised = Promise.promisify(someOperation);

someOperationPromised.timeout(1).then(function (){
console.log("Finished!");
}).catch(Promise.TimeoutError, function (err) {
throw err;
});

Answer

Not sure what promise library you're using, if any. May I suggest Bluebird? It's faster than native and has a lot of great features. Among them? Timeout.

From the documentation:

var Promise = require("bluebird");
var fs = Promise.promisifyAll(require('fs'));
fs.readFileAsync("huge-file.txt").timeout(100).then(function(fileContents) {

}).catch(Promise.TimeoutError, function(e) {
    console.log("could not read file within 100ms");
});

Edit:

Hey there! I took some time sorting through this, trying to figure out why your edit didn't work. My goal is to prove to you that .timeout() works. Moreover, I would like to provide you with a workable solution you can use moving forward with Bluebird.

Below I include a function I've named takesFourSeconds. It returns a promise that will resolve after 4 seconds. I then call takesFourSeconds twice. The first time I use timeout to force the promise chain to reject if it takes more than 3 seconds. The second time I force it to reject if it takes longer than 5 seconds.

var Promise = require("bluebird");

function takesFourSeconds (){
    return new Promise((resolve, reject) => {
        setTimeout(function(){
            return resolve('hi');
        }, 4000);
    });
}

takesFourSeconds().timeout(3000).then(function(res) {
    console.log(res);
}).catch(Promise.TimeoutError, function(e) {
    console.log("promise took longer than 3 seconds");
});

takesFourSeconds().timeout(5000).then(function(res) {
    console.log(res);
}).catch(Promise.TimeoutError, function(e) {
    console.log("promise took longer than 5 seconds");
});

Note that this returns:

$ node index.js
promise took longer than 3 seconds
hi

As expected.

Small side note:

When creating a function that returns a promise, you don't have to create a new promise for every function you call in a chain of promises. Only the first function.

For example, if I wanted to call another function after takesFourSeconds() I could write it like this:

function myFunc(result){
  return result === 'hi' ? resolve('hey there') : reject('hi there');
}

then:

takesFourSeconds()
  .then(myFunc) // myFunc will take in the resolved value of takesFourSeconds implicitly
  .then(result => console.log(result)
  .catch(error => console.log(error);

This should output:

"hey there"

There you have it! Timeouts in Bluebird.js. :)

Comments