2025-04-02 06:50:39 -04:00

243 lines
4.4 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);
}