index.js (3686B)
1 /*! 2 * on-finished 3 * Copyright(c) 2013 Jonathan Ong 4 * Copyright(c) 2014 Douglas Christopher Wilson 5 * MIT Licensed 6 */ 7 8 'use strict' 9 10 /** 11 * Module exports. 12 * @public 13 */ 14 15 module.exports = onFinished 16 module.exports.isFinished = isFinished 17 18 /** 19 * Module dependencies. 20 * @private 21 */ 22 23 var first = require('ee-first') 24 25 /** 26 * Variables. 27 * @private 28 */ 29 30 /* istanbul ignore next */ 31 var defer = typeof setImmediate === 'function' 32 ? setImmediate 33 : function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) } 34 35 /** 36 * Invoke callback when the response has finished, useful for 37 * cleaning up resources afterwards. 38 * 39 * @param {object} msg 40 * @param {function} listener 41 * @return {object} 42 * @public 43 */ 44 45 function onFinished(msg, listener) { 46 if (isFinished(msg) !== false) { 47 defer(listener, null, msg) 48 return msg 49 } 50 51 // attach the listener to the message 52 attachListener(msg, listener) 53 54 return msg 55 } 56 57 /** 58 * Determine if message is already finished. 59 * 60 * @param {object} msg 61 * @return {boolean} 62 * @public 63 */ 64 65 function isFinished(msg) { 66 var socket = msg.socket 67 68 if (typeof msg.finished === 'boolean') { 69 // OutgoingMessage 70 return Boolean(msg.finished || (socket && !socket.writable)) 71 } 72 73 if (typeof msg.complete === 'boolean') { 74 // IncomingMessage 75 return Boolean(msg.upgrade || !socket || !socket.readable || (msg.complete && !msg.readable)) 76 } 77 78 // don't know 79 return undefined 80 } 81 82 /** 83 * Attach a finished listener to the message. 84 * 85 * @param {object} msg 86 * @param {function} callback 87 * @private 88 */ 89 90 function attachFinishedListener(msg, callback) { 91 var eeMsg 92 var eeSocket 93 var finished = false 94 95 function onFinish(error) { 96 eeMsg.cancel() 97 eeSocket.cancel() 98 99 finished = true 100 callback(error) 101 } 102 103 // finished on first message event 104 eeMsg = eeSocket = first([[msg, 'end', 'finish']], onFinish) 105 106 function onSocket(socket) { 107 // remove listener 108 msg.removeListener('socket', onSocket) 109 110 if (finished) return 111 if (eeMsg !== eeSocket) return 112 113 // finished on first socket event 114 eeSocket = first([[socket, 'error', 'close']], onFinish) 115 } 116 117 if (msg.socket) { 118 // socket already assigned 119 onSocket(msg.socket) 120 return 121 } 122 123 // wait for socket to be assigned 124 msg.on('socket', onSocket) 125 126 if (msg.socket === undefined) { 127 // node.js 0.8 patch 128 patchAssignSocket(msg, onSocket) 129 } 130 } 131 132 /** 133 * Attach the listener to the message. 134 * 135 * @param {object} msg 136 * @return {function} 137 * @private 138 */ 139 140 function attachListener(msg, listener) { 141 var attached = msg.__onFinished 142 143 // create a private single listener with queue 144 if (!attached || !attached.queue) { 145 attached = msg.__onFinished = createListener(msg) 146 attachFinishedListener(msg, attached) 147 } 148 149 attached.queue.push(listener) 150 } 151 152 /** 153 * Create listener on message. 154 * 155 * @param {object} msg 156 * @return {function} 157 * @private 158 */ 159 160 function createListener(msg) { 161 function listener(err) { 162 if (msg.__onFinished === listener) msg.__onFinished = null 163 if (!listener.queue) return 164 165 var queue = listener.queue 166 listener.queue = null 167 168 for (var i = 0; i < queue.length; i++) { 169 queue[i](err, msg) 170 } 171 } 172 173 listener.queue = [] 174 175 return listener 176 } 177 178 /** 179 * Patch ServerResponse.prototype.assignSocket for node.js 0.8. 180 * 181 * @param {ServerResponse} res 182 * @param {function} callback 183 * @private 184 */ 185 186 function patchAssignSocket(res, callback) { 187 var assignSocket = res.assignSocket 188 189 if (typeof assignSocket !== 'function') return 190 191 // res.on('socket', callback) is broken in 0.8 192 res.assignSocket = function _assignSocket(socket) { 193 assignSocket.call(this, socket) 194 callback(socket) 195 } 196 }