Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add timeout wrapper for any callback interface #587

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ Usage:
* [`cargo`](#cargo)
* [`auto`](#auto)
* [`retry`](#retry)
* [`timeout`](#timeout)
* [`iterator`](#iterator)
* [`apply`](#apply)
* [`nextTick`](#nextTick)
Expand Down Expand Up @@ -1383,6 +1384,58 @@ async.auto({
});
```

---------------------------------------

<a name="timeout" />
### timeout(time, fn)

Make function `fn` timeout after a certain `time` if not called meanwhile. When a timeout happens, the function is called with `err` as first parameter, which contains error code `ETIMEDOUT`. This can be particularly useful for controlling callbacks that need to happen within a time window, and you just need to wrap that function with `timeout`, returning a *timed function*.

If the *timed function* is called after a timeout, that call is ignored.

__Arguments__

* `time` - How long in miliseconds before timing out `fn`.
* `fn` - The function that you'd like to control. An error with code `ETIMEDOUT` is passed to the function if timeout happens.

Example of a function that never times out:

```js
var fn = function (err) {
if (err) {
throw err;

// note that you could even check for err.code === 'ETIMEDOUT'
}

console.log('called back!');
};

// function never times out because it is called before the 200 ms limit
setTimeout(async.timeout(200, fn), 100);
```

Output would be: `'called back!'`

Example of a function that times out:

```js
var fn = function (err) {
if (err) {
console.log('Ups:', err.code);
throw err;
}

console.log('this never happens')
};


// function times out
setTimeout(async.timeout(100, fn), 200);
```

Output would be: `'Ups: ETIMEDOUT'`


---------------------------------------

Expand Down
28 changes: 28 additions & 0 deletions lib/async.js
Original file line number Diff line number Diff line change
Expand Up @@ -1123,4 +1123,32 @@
root.async = async;
}

async.timeout = function (maxTime, fn) {
var alreadyReturned = false,
timeout;

timeout = setTimeout(function () {
var err = new Error('Callback function timed out.');
err.code = 'ETIMEDOUT';

alreadyReturned = true;

return fn(err);
}, maxTime);

return function () {
if (alreadyReturned) {
return;
}

// mark callback as called
alreadyReturned = true;

clearTimeout(timeout);

// callback
fn.apply(root, arguments);
};
};

}());
93 changes: 84 additions & 9 deletions test/test-async.js
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ exports['retry as an embedded task'] = function(test) {
var retryResult = 'RETRY';
var fooResults;
var retryResults;

async.auto({
foo: function(callback, results){
fooResults = results;
Expand Down Expand Up @@ -2808,38 +2808,38 @@ exports['cargo bulk task'] = function (test) {
};

exports['cargo drain once'] = function (test) {

var c = async.cargo(function (tasks, callback) {
callback();
}, 3);

var drainCounter = 0;
c.drain = function () {
drainCounter++;
}

for(var i = 0; i < 10; i++){
c.push(i);
}

setTimeout(function(){
test.equal(drainCounter, 1);
test.done();
}, 500);
};

exports['cargo drain twice'] = function (test) {

var c = async.cargo(function (tasks, callback) {
callback();
}, 3);

var loadCargo = function(){
for(var i = 0; i < 10; i++){
c.push(i);
}
};

var drainCounter = 0;
c.drain = function () {
drainCounter++;
Expand Down Expand Up @@ -3163,11 +3163,86 @@ exports['queue started'] = function(test) {

var calls = [];
var q = async.queue(function(task, cb) {});

test.equal(q.started, false);
q.push([]);
test.equal(q.started, true);
test.done();

};

exports['time out happens'] = function (test) {
test.expect(2);

var a = 0;

async.timeout(200, function (err) {
if (err) {
test.equal(err.code, 'ETIMEDOUT');
}

test.equal(1, ++a);

test.done();
});
};


exports['time out happens and func called afterwards'] = function (test) {
test.expect(2);

var a = 0;

var timedFn = async.timeout(100, function (err) {
if (err) {
test.equal(err.code, 'ETIMEDOUT');
}

test.equal(1, ++a);

test.done();
});

setTimeout(timedFn, 200);
};

exports['time out does not happen'] = function (test) {
test.expect(1);

var a = 0;

var fn = function (err) {
if (err) {
return test.done(err);
}

test.equal(1, ++a);

test.done();
};

var timedFn = async.timeout(100, fn);

timedFn();
};

exports['time out does not happen and func is called multiple times'] = function (test) {
test.expect(1);

var a = 0;

var fn = function (err) {
if (err) {
return test.done(err);
}

test.equal(1, ++a);

test.done();
};

var timedFn = async.timeout(100, fn);

timedFn();
timedFn();
};