forked from tmthrgd/mpw-js
-
Notifications
You must be signed in to change notification settings - Fork 4
/
setImmediate-polyfill.js
200 lines (165 loc) · 8.17 KB
/
setImmediate-polyfill.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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
// setImmediate is a 0-delay setTimeout of sorts introduced
// by MS and wrongly held back by other browsers
window.setImmediate || !function (global) {
// Get the global prototype to attach setImmediate to
let attachTo = Object.getPrototypeOf && Object.getPrototypeOf(global);
// If we couldn't get the prototype or setTimeout wasn't attached
// to the prototype we just attach to global
attachTo && attachTo.setTimeout || (attachTo = global);
// If the MS prefixed implementation exists, use it
if (global.msSetImmediate) {
return attachTo.setImmediate = global.msSetImmediate, attachTo.clearImmediate = global.msClearImmediate;
}
// https://github.com/YuzuJS/setImmediate/blob/master/setImmediate.js
// This checks if the current environment is Node.js
if (global.process && Object.prototype.toString(global.process) === "[object process]") {
// If it is we might be able to use timers
let timers = global.require("timers");
// If it implements setImmediate we use it
if (timers && timers.setImmediate) {
return attachTo.setImmediate = timers.setImmediate, attachTo.clearImmediate = timers.clearImmediate;
}
// If it isn't we polyfill with nextTick which is
// sufficiently similar
if (global.process.nextTick) {
return attachTo.setImmediate = function (func, ...params) {
// Invoke func with the params as passed into setImmediate
global.process.nextTick(() => func(...params));
}, attachTo.clearImmediate = function (immediateID) {
// There is no id or way to stop nextTick
throw new Error("clearImmediate not implemented");
};
}
}
// http://dbaron.org/log/20100309-faster-timeouts
// https://github.com/YuzuJS/setImmediate/blob/master/setImmediate.js
// https://github.com/kriskowal/q/blob/0428c15d2ffc8e874b4be3a50e92884ef8701a6f/q.js#L125-141
// If we have messaging channels, or we have postMessage and this
// isn't a WebWorker, we can use messaging
if (global.MessageChannel || global.postMessage && !global.importScripts && (function () {
// This checks if global.postMessage is asynchronous,
// it has been known to be buggy and synchronous in
// some browsers
let postMessageIsAsynchronous = true;
let oldOnMessage = global.onmessage;
global.onmessage = function () { postMessageIsAsynchronous = false; };
global.postMessage("", "*");
global.onmessage = oldOnMessage;
return postMessageIsAsynchronous;
})()) {
// A unique id prefix to ensure that ONLY valid messages are accepted
let messageName = ("setImmediate-polyfill-" + Math.random()).replace("0.", "");
// The numeric identifier of the next dispatched scrypt call
let immediateID = 1;
// The timeout function and arguments, indexed by numeric identifier
let timeouts = { };
// If a MessageChannel exists we can use it to avoid sending
// messages to the browser which could cause interoperability
// issues
let channel = global.MessageChannel && new global.MessageChannel();
// We need to start port1 in order to receive messages sent
// from port2
channel && channel.port1.start();
// Add a handler to the message event of either the message
// channel, if it exists, or global if it does not
(channel && channel.port1 || global).addEventListener("message", function (event) {
// Split the identifier into the name and numeric id
let [name, immediateID] = event.data.split("$");
// If we are not using a MessageChannel check that the source
// of the event was this window, also check the name is valid,
// if either of these are not true, we didn't send it
if (!channel && event.source !== global || name !== messageName) {
return;
}
// Prevent the event from propagating further
event.stopPropagation();
// Retrieve the function and the arguments we will invoke
// leaving func and params as null if the immediateID
// does not exist in timeouts (because clearImmediate has
// been called before we got here)
let [func, params] = timeouts[immediateID] || [ ];
// Invoke the func with the appropriate parameters
func && func(...params);
// Clear func and params for GC
func = params = null;
// Remove key:immediateID from timeouts to ensure it's only
// called once and to allow for GC
delete timeouts[immediateID];
}, false);
return attachTo.setImmediate = function (func, ...params) {
// Store the function and it's arguments in timeouts
timeouts[immediateID] = [func, params];
// Post the message either using port2 of the MessageChannel
// or on global if it's not available w/ the unique id
// If the message is sent on global we dispatch it w/ a
// targetOrigin of "*" (indicating no preference)
(channel && channel.port2 || global).postMessage([messageName, immediateID].join("$"), ...(channel ? [ ] : ["*"]));
// We return a unique numeric id to identify the call
// to setImmediate, this allows it to be cancelled
return immediateID++;
}, attachTo.clearImmediate = function (immediateID) {
// Delete the function and arguments associated
// w/ identifier of immediateID
delete timeouts[immediateID];
};
}
// Set setImmediate to prefixed or non-prefixed requestAnimationFrame
// requestAnimationFrame dispatches at a later point in the event cycle
attachTo.setImmediate = global.requestAnimationFrame || global.mozRequestAnimationFrame || global.webkitRequestAnimationFrame || global.msRequestAnimationFrame;
// If requestAnimationFrame existed we end, setting clearImmediate
// to cancelAnimationFrame
if (attachTo.setImmediate) {
return attachTo.clearImmediate = global.cancelAnimationFrame || global.mozCancelAnimationFrame || global.webkitCancelAnimationFrame || global.msCancelAnimationFrame || global.webkitCancelRequestAnimationFrame;
}
// https://github.com/YuzuJS/setImmediate/blob/master/setImmediate.js
// We can use a script tag and the readystatechange event on IE(?)
if (global.document && "onreadystatechange" in global.document.getElementsByTagName("script")[0]) {
// The numeric identifier of the next dispatched scrypt call
let immediateID = 1;
// A boolean value to allow clearImmediate to work,
// indexed by numeric identifier
let timeouts = { };
return attachTo.setImmediate = function (func, ...params) {
// Set true in timeouts for immediateID to indicate the func
// should be invoked
timeouts[immediateID] = true;
// Create a script tag that will be added to the DOM
let script = global.document.createElement("script");
// Add a handler for onreadystatechange
script.onreadystatechange = function () {
// If the timeout has not been cancelled, call the func
// w/ the arguments specified
timeouts[immediateID] && func(...params);
// Remove key:immediateID from timeouts to ensure it's only called once
delete timeouts[immediateID];
// Remove the handler to allow GC
script.onreadystatechange = null;
// Remove the script tag from the DOM to ensure GC
global.document.body.removeChild(script);
// Nullify the script variable to allow GC
script = null;
};
// Add the script tag to the DOM to which begins loading
// the tag which will invoke the readystatechange event
global.document.body.appendChild(script);
// Return a unique numeric id to identify the call
// to setImmediate, this allows it to be cancelled
return immediateID++;
}, attachTo.clearImmediate = function (immediateID) {
// Remove key:immediateID from timeouts to prevent func from being called
delete timeouts[immediateID];
};
}
// The worst fallback is setImmediate, although the delay is set to 0
// in reality this should have a ~20ms delay, this is an important part
// of the spec
attachTo.setImmediate = (func, ...params) => global.setTimeout(func, 0, ...params);
attachTo.clearImmediate = global.clearTimeout;
// Here we check if the arguments passed to setTimeout actually will be
// passed to the callback, on older versions of IE(?) this check will fail
global.setTimeout(function (arg) {
// If the test fails, we wrap func in a closure that will invoke func w/
// the arguments
arg || (attachTo.setImmediate = (func, ...params) => global.setTimeout(() => func(...params), 0));
}, 0, true);
}(this);