-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
165 lines (143 loc) · 4.24 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
'use strict';
var response = require('xhr-response')
, statuscode = require('xhr-status')
, one = require('one-time')
, fail = require('failure');
/**
* Simple nope function that assigned to XHR requests as part of a clean-up
* operation.
*
* @api private
*/
function nope() {}
/**
* Attach various of event listeners to a given XHR request.
*
* @param {XHR} xhr A XHR request that requires listening.
* @param {EventEmitter} ee EventEmitter that receives events.
* @api public
*/
function loads(xhr, ee) {
var onreadystatechange
, onprogress
, ontimeout
, onabort
, onerror
, onload
, timer;
/**
* Error listener.
*
* @param {Event} evt Triggered error event.
* @api private
*/
onerror = xhr.onerror = one(function onerror(evt) {
var status = statuscode(xhr)
, err = fail(new Error('Network request failed'), status);
ee.emit('error', err);
ee.emit('end', err, status);
});
/**
* Fix for FireFox's odd abort handling behaviour. When you press ESC on an
* active request it triggers `error` instead of abort. The same is called
* when an HTTP request is canceled onunload.
*
* @see https://bugzilla.mozilla.org/show_bug.cgi?id=768596
* @see https://bugzilla.mozilla.org/show_bug.cgi?id=880200
* @see https://code.google.com/p/chromium/issues/detail?id=153570
* @param {Event} evt Triggerd abort event
* @api private
*/
onabort = xhr.onabort = function onabort(evt) {
onerror(evt);
};
/**
* ReadyStateChange listener.
*
* @param {Event} evt Triggered readyState change event.
* @api private
*/
onreadystatechange = xhr.onreadystatechange = function change(evt) {
var target = evt.target;
if (4 === target.readyState) return onload(evt);
};
/**
* The connection has timed out.
*
* @api private
*/
ontimeout = xhr.ontimeout = one(function timeout(evt) {
ee.emit('timeout', evt);
//
// Make sure that the request is aborted when there is a timeout. If this
// doesn't trigger an error, the next call will.
//
if (xhr.abort) xhr.abort();
onerror(evt);
});
//
// Fallback for implementations that did not ship with timer support yet.
// Microsoft's XDomainRequest was one of the first to ship with `.timeout`
// support so we all XHR implementations before that require a polyfill.
//
// @see https://bugzilla.mozilla.org/show_bug.cgi?id=525816
//
if (xhr.timeout) timer = setTimeout(ontimeout, +xhr.timeout);
/**
* IE needs have it's `onprogress` function assigned to a unique function. So,
* no touchy touchy here!
*
* @param {Event} evt Triggered progress event.
* @api private
*/
onprogress = xhr.onprogress = function progress(evt) {
var status = statuscode(xhr)
, data;
ee.emit('progress', evt, status);
if (xhr.readyState >= 3 && status.code === 200 && (data = response(xhr))) {
ee.emit('stream', data, status);
}
};
/**
* Handle load events an potential data events for when there was no streaming
* data.
*
* @param {Event} evt Triggered load event.
* @api private
*/
onload = xhr.onload = one(function load(evt) {
var status = statuscode(xhr)
, data = response(xhr);
if (status.code < 100 || status.code > 599) return onerror(evt);
//
// There is a bug in FireFox's XHR2 implementation where status code 204
// triggers a "no element found" error and bad data. So to be save here,
// we're just **never** going to emit a `stream` event as for 204's there
// shouldn't be any content.
//
// @see https://bugzilla.mozilla.org/show_bug.cgi?id=521301
//
if (data && status.code !== 204) {
ee.emit('stream', data, status);
}
ee.emit('end', undefined, status);
});
//
// Properly clean up the previously assigned event listeners and timers to
// prevent potential data leaks and unwanted `stream` events.
//
ee.once('end', function cleanup() {
xhr.onreadystatechange = onreadystatechange =
xhr.onprogress = onprogress =
xhr.ontimeout = ontimeout =
xhr.onerror = onerror =
xhr.onabort = onabort =
xhr.onload = onload = nope;
if (timer) clearTimeout(timer);
});
return xhr;
}
//
// Expose all the things.
//
module.exports = loads;