A serial queue for asynchronous operations in Javascript
Let’s get straight to the point:
Of course, you could just use something like Async.js’s
queue
. But we suffer from
NIH here!
I couldn’t find much documentation on the semantics of Promise
, but
it seems that the promise’s function parameter is executed before
the function returns. The thunk is necessary to work around this, to
avoid executing the queued action (item
) immediately.
Also, there’s no provision for handling failing actions. Not
production code, blah blah blah. See update note at the top of this
article.
It has 3 interfaces:
-
Queue an action that takes 2 parameters (identical to the signature for the parameter to
Promise
). The action must eventually call one ofresolve
orreject
, otherwise the queue will deadlock. -
Queue an action that takes no parameters, and returns a promise (or rather, something that is thenable).
-
Queue an action that returns nothing, or a value without a
then
attribute. This case is assumed to indicate a synchronous action.
Uses 1 and 2 are for asynchronous actions, that will not have
completed when the function to trigger the action returns (for
example, an XMLHTTRequest
). In these cases, give the action a way
(well, two) to signal completion once the action does complete.
“Show Your Working”
I decided to use Javascript promises, because I never need much motivation to make things overly complex.
My first hack used an array to store each queued action as a promise, but I realised that the only part of the array that is every useful is the last item. So we only need to keep the most recent action.
But, how do we start it off? Well, we start with a Hah, no need! We’re using promises, so
we can just start off with an already completed action, via
null
queue, and
then have a condition that…Promise.resolve()
:
Next, we just need to keep track of the the end of the queue. This only changes when we enqueue an action. To rephrase the problem:
Perform the action passed to
enqueue()
after the currently executing action completes.
Luckily, it appears that anything passed to then()
of an already
resolved promise will not be executed until the next time through the
Javascript event loop:
Results in output of:
inside
outside
then-ed
So,
-
Throw our action’s new promise (technically, the
thunk
) atthen()
of the current promise. -
Make the new promise the one we mean when we say the currently executing action. We can do this because from the point of view of the enqueued action, all of the previous actions are the currently executing action. Serial queue, remember?
And because of Javascript’s wonderful single-threaded-ness, we can just swap the variables over.
For setting up the thunk, we can take advantage of the reason we need the thunk to help us:
We need to create a function that will execute the action, and then resolve the promise. But we can’t do that until we’re in the middle of creating the promise. We can actually tease this apart, so that it’s clearer what is going on:
So we use the promise as the end of the queue, but actually use the thunk as the thing that is executed when the current action completes.
Put it all together, et voilà. A queue that will serialise asynchronous actions. No warranty expressed or implied.