You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
234 lines
4.3 KiB
234 lines
4.3 KiB
/*! |
|
* on-finished |
|
* Copyright(c) 2013 Jonathan Ong |
|
* Copyright(c) 2014 Douglas Christopher Wilson |
|
* MIT Licensed |
|
*/ |
|
|
|
'use strict' |
|
|
|
/** |
|
* Module exports. |
|
* @public |
|
*/ |
|
|
|
module.exports = onFinished |
|
module.exports.isFinished = isFinished |
|
|
|
/** |
|
* Module dependencies. |
|
* @private |
|
*/ |
|
|
|
var asyncHooks = tryRequireAsyncHooks() |
|
var first = require('ee-first') |
|
|
|
/** |
|
* Variables. |
|
* @private |
|
*/ |
|
|
|
/* istanbul ignore next */ |
|
var defer = typeof setImmediate === 'function' |
|
? setImmediate |
|
: function (fn) { process.nextTick(fn.bind.apply(fn, arguments)) } |
|
|
|
/** |
|
* Invoke callback when the response has finished, useful for |
|
* cleaning up resources afterwards. |
|
* |
|
* @param {object} msg |
|
* @param {function} listener |
|
* @return {object} |
|
* @public |
|
*/ |
|
|
|
function onFinished (msg, listener) { |
|
if (isFinished(msg) !== false) { |
|
defer(listener, null, msg) |
|
return msg |
|
} |
|
|
|
// attach the listener to the message |
|
attachListener(msg, wrap(listener)) |
|
|
|
return msg |
|
} |
|
|
|
/** |
|
* Determine if message is already finished. |
|
* |
|
* @param {object} msg |
|
* @return {boolean} |
|
* @public |
|
*/ |
|
|
|
function isFinished (msg) { |
|
var socket = msg.socket |
|
|
|
if (typeof msg.finished === 'boolean') { |
|
// OutgoingMessage |
|
return Boolean(msg.finished || (socket && !socket.writable)) |
|
} |
|
|
|
if (typeof msg.complete === 'boolean') { |
|
// IncomingMessage |
|
return Boolean(msg.upgrade || !socket || !socket.readable || (msg.complete && !msg.readable)) |
|
} |
|
|
|
// don't know |
|
return undefined |
|
} |
|
|
|
/** |
|
* Attach a finished listener to the message. |
|
* |
|
* @param {object} msg |
|
* @param {function} callback |
|
* @private |
|
*/ |
|
|
|
function attachFinishedListener (msg, callback) { |
|
var eeMsg |
|
var eeSocket |
|
var finished = false |
|
|
|
function onFinish (error) { |
|
eeMsg.cancel() |
|
eeSocket.cancel() |
|
|
|
finished = true |
|
callback(error) |
|
} |
|
|
|
// finished on first message event |
|
eeMsg = eeSocket = first([[msg, 'end', 'finish']], onFinish) |
|
|
|
function onSocket (socket) { |
|
// remove listener |
|
msg.removeListener('socket', onSocket) |
|
|
|
if (finished) return |
|
if (eeMsg !== eeSocket) return |
|
|
|
// finished on first socket event |
|
eeSocket = first([[socket, 'error', 'close']], onFinish) |
|
} |
|
|
|
if (msg.socket) { |
|
// socket already assigned |
|
onSocket(msg.socket) |
|
return |
|
} |
|
|
|
// wait for socket to be assigned |
|
msg.on('socket', onSocket) |
|
|
|
if (msg.socket === undefined) { |
|
// istanbul ignore next: node.js 0.8 patch |
|
patchAssignSocket(msg, onSocket) |
|
} |
|
} |
|
|
|
/** |
|
* Attach the listener to the message. |
|
* |
|
* @param {object} msg |
|
* @return {function} |
|
* @private |
|
*/ |
|
|
|
function attachListener (msg, listener) { |
|
var attached = msg.__onFinished |
|
|
|
// create a private single listener with queue |
|
if (!attached || !attached.queue) { |
|
attached = msg.__onFinished = createListener(msg) |
|
attachFinishedListener(msg, attached) |
|
} |
|
|
|
attached.queue.push(listener) |
|
} |
|
|
|
/** |
|
* Create listener on message. |
|
* |
|
* @param {object} msg |
|
* @return {function} |
|
* @private |
|
*/ |
|
|
|
function createListener (msg) { |
|
function listener (err) { |
|
if (msg.__onFinished === listener) msg.__onFinished = null |
|
if (!listener.queue) return |
|
|
|
var queue = listener.queue |
|
listener.queue = null |
|
|
|
for (var i = 0; i < queue.length; i++) { |
|
queue[i](err, msg) |
|
} |
|
} |
|
|
|
listener.queue = [] |
|
|
|
return listener |
|
} |
|
|
|
/** |
|
* Patch ServerResponse.prototype.assignSocket for node.js 0.8. |
|
* |
|
* @param {ServerResponse} res |
|
* @param {function} callback |
|
* @private |
|
*/ |
|
|
|
// istanbul ignore next: node.js 0.8 patch |
|
function patchAssignSocket (res, callback) { |
|
var assignSocket = res.assignSocket |
|
|
|
if (typeof assignSocket !== 'function') return |
|
|
|
// res.on('socket', callback) is broken in 0.8 |
|
res.assignSocket = function _assignSocket (socket) { |
|
assignSocket.call(this, socket) |
|
callback(socket) |
|
} |
|
} |
|
|
|
/** |
|
* Try to require async_hooks |
|
* @private |
|
*/ |
|
|
|
function tryRequireAsyncHooks () { |
|
try { |
|
return require('async_hooks') |
|
} catch (e) { |
|
return {} |
|
} |
|
} |
|
|
|
/** |
|
* Wrap function with async resource, if possible. |
|
* AsyncResource.bind static method backported. |
|
* @private |
|
*/ |
|
|
|
function wrap (fn) { |
|
var res |
|
|
|
// create anonymous resource |
|
if (asyncHooks.AsyncResource) { |
|
res = new asyncHooks.AsyncResource(fn.name || 'bound-anonymous-fn') |
|
} |
|
|
|
// incompatible node.js |
|
if (!res || !res.runInAsyncScope) { |
|
return fn |
|
} |
|
|
|
// return bound function |
|
return res.runInAsyncScope.bind(res, fn, null) |
|
}
|
|
|