Skip to content

Instantly share code, notes, and snippets.

@cmawhorter
Created December 23, 2015 03:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cmawhorter/86c576a597479ba366bc to your computer and use it in GitHub Desktop.
Save cmawhorter/86c576a597479ba366bc to your computer and use it in GitHub Desktop.
Wait for all values in state to be true before calling callback or error on timeout.
// similar usecase; abbreviated
var myTask = function(callback) {
var state = { response: false, body: false, other: null }; // only t/f factored when enableNulls
var abortWait = waitForState(state, callback); // returns function to abort with error
var req = request('https://...');
req.on('response', function(res) {
asyncLogResponse(function(err) {
if (err) return abortWait(err); // aborts wait and callbacks immediately
state.response = true;
});
});
// ... similar for state.body ...
// once both return invokes callback or an error if it times out (after 60s)
};
'use strict';
// License: Public Domain or MIT
// wraps async.until specifically for this usecase; with some options
var async = require('async');
function waitForState(state, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
var timeout = options.timeout || 60000
, pollEvery = options.pollEvery || 1000
, enableNulls = typeof options.enableNulls === 'boolean' ? options.enableNulls : true
, log = options.log || function(){}
, start = new Date().getTime()
, _aborted = false;
log('waitForState options', timeout, pollEvery, enableNulls);
log('started at', start);
async.until(function() {
if (_aborted) {
return true;
}
log('entering state test');
var workIsComplete = true;
for (var k in state) {
var val = state[k];
log('\t-> iterating %s = %s', k, val);
if (!enableNulls || null !== val) {
log('\t-> testing %s = %s', k, val);
workIsComplete = workIsComplete && val;
log('\t<- complete?', workIsComplete);
}
}
log('leaving state test', workIsComplete);
return workIsComplete;
}, function(taskCallback) {
setTimeout(function() {
var elapsed = new Date().getTime() - start
, isTimedOut = elapsed < timeout;
log('entering task');
log('\t-> elapsed %s less than timeout %s?', elapsed, timeout, isTimedOut);
taskCallback(isTimedOut ? null : new Error('timed out while waiting for state'));
}, pollEvery);
}, function() {
if (!_aborted) {
callback.apply(this, arguments);
}
});
return function abortWaitForState(err) {
_aborted = true;
callback(err || new Error('wait aborted'));
};
}
// caolan/async has a lot but it doesn't have a baked in way to to do this?
// Example usecase:
// You have a task that is reading from a remote stream and need to do an async log
// multiple times along the way. How can you be sure all logs completed successfully
// before invoking the callback?
var myTask = function(callback) {
var req = request('https://...');
req.on('response', asyncLogResponse);
req.on('error', asyncLogError);
req.pipe(asyncLogBodyStream).on('end', asyncLogEnd);
// when can we invoke callback() ?
// async.parallel would work, unless one of our async tasks never returns
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment