the callback can go cause trouble, calling other functions, including awaitCallback. this is a non-starter.
instead, something like this might be the place to start:
var awaiter = new Awaiter;
setTimeout(awaiter, 100);
// any args that would have been passed to the callback are returned
var args = awaiter.wait(200);
the key is that no JS is executed while waiting. everything happens inside the host.
the issue here is the semantics of the event loop in browsers and the run-to-completion semantics of JS. something will have to give here.
we have to deal with the fact that suspending a running callback (or script) and returning to the event loop is not a thing that the spec knows how to do.
a couple ideas:
- spawn a new event loop and copy across the awaited tasks. this breaks b/c
tasks do not necessarily get scheduled until something happens. iow, the task
may not be extant at the time of the call to
wait
. so, we will have to run the other event loop too, but schedule in our new one instead when stuff happens - or, stick in the same event loop, but freeze all the queues and make new ones for the awaited task sources. as tasks are added, if they correspond to awaited on stuff, then instead enqueue them on the new queues
One of the original motivating use cases for Web Workers was doing synchronous IO off the main thread (1). With the exception of synchronous XHR, no synchronous IO APIs have been implemented by more than one major browser. Discussion of the merits and difficulties of implementing sync APIs has been repeated and heated (2, 3, 4, 5, 6, 7, 8).
- discussion has thus far focused on sync versions of existing apis
- it seems like a bummer to have two apis to support
- nevertheless, there are serious usecases: emscripten, jsil, etc.
What if code running in a Web Worker could instead simply block until an async operation completes? This would add a minimal, low-level capability to workers and make it possible to implement synchronous APIs in user code.
- how can we do that?
- all async ops are implemented by host objects
- all async ops accept callbacks
- so, blocking on a callback is equivalent to blocking on the operation
The awaitCallback
method accepts one or more functions and a timeout in
milliseconds. Each function must be a currently pending callback registered with
a host object. awaitCallback
blocks until one of the passed functions
is called or the specified timeout expires.
If a callback function is called, awaitCallback
returns an array containing
the return value of the callback function at the same index as the callback
function was passed to awaitCallback
and undefined
in all other indices. If
the timeout expires, awaitCallback
throws an exception.
When a callback is called, awaitCallback
returns an array containing the
callback return value.
function onOneTimeout() {
return 'marco';
}
function onTheOtherTimeout() {
return 'polo'
}
setTimeout(onOneTimeout, 100);
setTimeout(onTheOtherTimeout, 200);
awaitCallback(onOneTimeout, onTheOtherTimeout, 500); // -> ['marco', undefined]
If a function is passed that is not a pending callback, awaitCallback
throws.
function onOneTimeout() {
return 'marco';
}
function onTheOtherTimeout() {
return 'polo'
}
setTimeout(onOneTimeout, 100);
awaitCallback(onOneTimeout, onTheOtherTimeout, 500); // throws
The following examples assume the existence of a console.log
polyfill in the
worker.
Sleep for a number of milliseconds.
function sleep(ms) {
var now = Date.now();
function onTimeout() {
return Date.now() - now;
}
setTimeout(onTimeout, ms);
return awaitCallback(onTimeout, 2 * ms);
}
console.log('slept for', sleep(1234), 'ms');
Wait for a postMessage
message.
function waitForMessage(timeout) {
function onMessage(e) {
removeEventListener('message', onMessage);
return e.data;
}
addEventListener('message', onMessage);
return awaitCallback(onMessage, timeout);
}
try {
console.log('waiting for a message');
console.log('got one:', waitForMessage(1000));
} catch (e) {
console.log('timed out waiting for a message');
}
Consider the following script:
setTimeout(function () {
console.log('one');
}, 100);
function two() {
console.log('two');
}
setTimeout(two, 200);
setTimeout(function () {
console.log('three');
}, 300);
awaitCallback(two, 250);
/* in the console:
* two
* one
* three
*/
Should this work?
function whoops() {
throw new Error('doh!');
}
setTimeout(whoops, 0);
try {
awaitCallback(whoops, 100);
} catch (e) {
console.log('saw an error:', e);
}