Date: | August 3, 2018 / year-entry #177 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20180803-00/?p=99405 |
Comments: | 2 |
Summary: | Rolling onward. |
Last time, we created an awaitable lock for C++ PPL tasks. Let's do the same thing for WinJS Promises (because I needed one of those too). var AwaitableLock = WinJS.Class.define(function AwaitableLock() { this._locked = false; this._p = null; this._c = null; }, { waitAsync: function waitAsync() { if (!this._locked) { // Lock is available. Acquire it. this._locked = true; return WinJS.Promise.wrap(); } // Lock is not available. var self = this; if (!this._p) { // Create a promise that completes when the lock is released. this._p = new WinJS.Promise(function (c) { self._c = c; }); } // Return a promise that waits for the lock to be released // and then retries the wait. return this._p.then(function () { return self.waitAsync(); }); }, release: function release() { this._locked = false; var c = this._c; this._p = null; this._c = null; // Complete any promise that was waiting for release. c && c(); } }); Since JavaScript is single-threaded, we don't have to worry about concurrency, so we don't need a mutex to guard our state variables. If the lock is available, then acquire it and return a completed promise.
Otherwise, we have to create a promise.
The
We then chain a continuation on that newly-created promise
that restarts the
When the lock is released, we clear the saved
completion and promise (thereby resetting the lock object
to its unlocked state),
and if there is a completion function, we call it.
This completes the promise we created in
There is a subtlety here: We reset the lock object completely
before calling the completion.
The completion is going to attempt to re-acquire
the lock object, and we don't want the reentrant call to see
a lock object in an inconsistent state.
To avoid that, we capture the completion
callback into a local We can use ES6 function arrow notation to simplify this code, but if we're going to do that, we may as well go all the way and use ES6 native promises. var AwaitableLock = WinJS.Class.define(function AwaitableLock() { this._locked = false; this._p = null; this._c = null; }, { waitAsync() { if (!this._locked) { // Lock is available. Acquire it. this._locked = true; return Promise.resolve(); } // Lock is not available. if (!this._p) { // Create a promise that completes when the lock is released. this._p = new Promise(c => this._c = c); } return this._p.then(() => this.waitAsync()); }, release() { this._locked = false; var c = this._c; this._p = null; this._c = null; // Complete any promise that was waiting for release. c && c(); } });
The ES6 Promise method for creating an already-completed promise
is called And since we're abandoning WinJS, we may as well go all the way and define the class using ES6's native class declaration syntax. class AwaitableLock { constructor() { this._locked = false; this._p = null; this._c = null; } waitAsync() { if (!this._locked) { // Lock is available. Acquire it. this._locked = true; return Promise.resolve(); } // Lock is not available. if (!this._p) { // Create a promise that completes when the lock is released. this._p = new Promise(c => this._c = c); } return this._p.then(() => this.waitAsync()); } release() { this._locked = false; var c = this._c; this._p = null; this._c = null; // Complete any promise that was waiting for release. c && c(); } } Exercise: Why couldn't we arrowize the constructor? Exercise 2: Why did we set the member variables dynamically in the constructor instead of defining them statically on the prototype?
Exercise 3:
Why couldn't I have precalculated the // Code in italics is wrong - but why? AwaitableLock.prototype.waitAsync = function waitAsync() { if (!this._locked) { // Lock is available. Acquire it. this._locked = true; return Promise.resolve(); } // Lock is not available. if (!this._p) { // Create a promise that completes when the lock is released // and then retries the wait. this._p = new Promise(c => this._c = c).then(() => this.waitAsync()); } return this._p; } |
Comments (2)
Comments are closed. |
1. It’s a constructor, so it needs to have its own new
this
.2. JS engines are optimised for accessing objects whose list of own property values doesn’t change after construction.
3. Each waiter needs to retry after the lock is released.
“Arrowizing is convenient because it preserves this inside the lambda, which saves us the trouble of having to create a separate self variable.”
How about binding ‘this’ to your function with .bind(this). Does that not do the same, or does it do something different underwater?