thedesk/app/node_modules/got/source/utils/timed-out.js
2019-09-12 23:38:13 +09:00

161 lines
4.3 KiB
JavaScript

'use strict';
const net = require('net');
class TimeoutError extends Error {
constructor(threshold, event) {
super(`Timeout awaiting '${event}' for ${threshold}ms`);
this.name = 'TimeoutError';
this.code = 'ETIMEDOUT';
this.event = event;
}
}
const reentry = Symbol('reentry');
const noop = () => {};
module.exports = (request, delays, options) => {
/* istanbul ignore next: this makes sure timed-out isn't called twice */
if (request[reentry]) {
return;
}
request[reentry] = true;
let stopNewTimeouts = false;
const addTimeout = (delay, callback, ...args) => {
// An error had been thrown before. Going further would result in uncaught errors.
// See https://github.com/sindresorhus/got/issues/631#issuecomment-435675051
if (stopNewTimeouts) {
return noop;
}
// Event loop order is timers, poll, immediates.
// The timed event may emit during the current tick poll phase, so
// defer calling the handler until the poll phase completes.
let immediate;
const timeout = setTimeout(() => {
immediate = setImmediate(callback, delay, ...args);
/* istanbul ignore next: added in node v9.7.0 */
if (immediate.unref) {
immediate.unref();
}
}, delay);
/* istanbul ignore next: in order to support electron renderer */
if (timeout.unref) {
timeout.unref();
}
const cancel = () => {
clearTimeout(timeout);
clearImmediate(immediate);
};
cancelers.push(cancel);
return cancel;
};
const {host, hostname} = options;
const timeoutHandler = (delay, event) => {
request.emit('error', new TimeoutError(delay, event));
request.once('error', () => {}); // Ignore the `socket hung up` error made by request.abort()
request.abort();
};
const cancelers = [];
const cancelTimeouts = () => {
stopNewTimeouts = true;
cancelers.forEach(cancelTimeout => cancelTimeout());
};
request.once('error', cancelTimeouts);
request.once('response', response => {
response.once('end', cancelTimeouts);
});
if (delays.request !== undefined) {
addTimeout(delays.request, timeoutHandler, 'request');
}
if (delays.socket !== undefined) {
const socketTimeoutHandler = () => {
timeoutHandler(delays.socket, 'socket');
};
request.setTimeout(delays.socket, socketTimeoutHandler);
// `request.setTimeout(0)` causes a memory leak.
// We can just remove the listener and forget about the timer - it's unreffed.
// See https://github.com/sindresorhus/got/issues/690
cancelers.push(() => request.removeListener('timeout', socketTimeoutHandler));
}
if (delays.lookup !== undefined && !request.socketPath && !net.isIP(hostname || host)) {
request.once('socket', socket => {
/* istanbul ignore next: hard to test */
if (socket.connecting) {
const cancelTimeout = addTimeout(delays.lookup, timeoutHandler, 'lookup');
socket.once('lookup', cancelTimeout);
}
});
}
if (delays.connect !== undefined) {
request.once('socket', socket => {
/* istanbul ignore next: hard to test */
if (socket.connecting) {
const timeConnect = () => addTimeout(delays.connect, timeoutHandler, 'connect');
if (request.socketPath || net.isIP(hostname || host)) {
socket.once('connect', timeConnect());
} else {
socket.once('lookup', error => {
if (error === null) {
socket.once('connect', timeConnect());
}
});
}
}
});
}
if (delays.secureConnect !== undefined && options.protocol === 'https:') {
request.once('socket', socket => {
/* istanbul ignore next: hard to test */
if (socket.connecting) {
socket.once('connect', () => {
const cancelTimeout = addTimeout(delays.secureConnect, timeoutHandler, 'secureConnect');
socket.once('secureConnect', cancelTimeout);
});
}
});
}
if (delays.send !== undefined) {
request.once('socket', socket => {
const timeRequest = () => addTimeout(delays.send, timeoutHandler, 'send');
/* istanbul ignore next: hard to test */
if (socket.connecting) {
socket.once('connect', () => {
request.once('upload-complete', timeRequest());
});
} else {
request.once('upload-complete', timeRequest());
}
});
}
if (delays.response !== undefined) {
request.once('upload-complete', () => {
const cancelTimeout = addTimeout(delays.response, timeoutHandler, 'response');
request.once('response', cancelTimeout);
});
}
};
module.exports.TimeoutError = TimeoutError;