|
|
/*! * 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) }
|