235 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			235 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | /*! | ||
|  |  * 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) | ||
|  | } |