I'm following the example in this article https://spin.atomicobject.com/2018/09/10/javascript-concurrency/:
What we need here is basically a mutex: a way to say that the critical section of reading the collection, updating it, and writing it back cannot be happening simultaneously. Let’s imagine we had such a thing [...]:
const collectionMutex = new Mutex(); async function set(collection: string, key: string, value: string): {[key: string]: string} { return await collectionMutex.dispatch(async () => { const data = await fetchCollection(collection); data[key] = val; await sendCollection(collection, data); return data; }); }
Implementing this mutex requires a bit of promise-trampoline-ing, but it’s still relatively straightforward:
class Mutex { private mutex = Promise.resolve(); lock(): PromiseLike<() => void> { let begin: (unlock: () => void) => void = unlock => {}; this.mutex = this.mutex.then(() => { return new Promise(begin); }); return new Promise(res => { begin = res; }); } async dispatch(fn: (() => T) | (() => PromiseLike<T>)): Promise<T> { const unlock = await this.lock(); try { return await Promise.resolve(fn()); } finally { unlock(); } } }
In the dispatch
function of the Mutex
class, unlock
is set to await this.lock()
.
My question is: how and why is unlock
a function when lock()
returns a Promise that doesn't resolve to anything; the Promise just sets begin = res
.
Here is how it works:
There are two promises involved that are created with new Promise
. One of them is the promise that lock
returns. Let's call that promise P1
. The other one is created later, in the callback passed to this.mutex.then
. Let's call that promise P2
.
res
is a function which, when called, resolves P1
. But it is not called immediately. Instead, begin
is made to reference that same function (begin = res
) so we can access it later.
When the callback given to this.mutex.then
gets executed (which is when the most recent lock is released), the main magic happens:
new Promise(begin)
will execute begin
. It looks strange, as normally you would provide an inline callback function where you would perform the logic that has some asynchronous dependency and then have it call resolve
-- the argument that this callback function gets. But here begin
is that callback function. You could write the creation of the P2
promise more verbose, by providing an inline function wrapper to the constructor, like this:
new Promise(resolve => begin(resolve));
As indicated above, calling begin
will resolve P1
. The argument passed to begin
will be the resolve
function that the promise constructor provides to us so we can resolve that new P2
promise. This (function) argument thus becomes the resolution value for P1
, and yes, it is a function. This is what the await
-ed expression resolves to. unlock
is thus a resolve
function for resolving P2
.