{"id":99405,"date":"2018-08-03T07:00:00","date_gmt":"2018-08-03T21:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/?p=99405"},"modified":"2019-03-13T00:37:50","modified_gmt":"2019-03-13T07:37:50","slug":"20180803-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20180803-00\/?p=99405","title":{"rendered":"Creating an awaitable lock for WinJS and JavaScript Promises"},"content":{"rendered":"<p><a HREF=\"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/20180802-00\/?p=99395\">Last time<\/a>, we created an awaitable lock for C++ PPL tasks. Let&#8217;s do the same thing for WinJS Promises (because I needed one of those too). <\/p>\n<pre>\nvar AwaitableLock = WinJS.Class.define(function AwaitableLock() {\n  this._locked = false;\n  this._p = null;\n  this._c = null;\n}, {\n  waitAsync: function waitAsync() {\n    if (!this._locked) {\n      \/\/ Lock is available. Acquire it.\n      this._locked = true;\n      return WinJS.Promise.wrap();\n    }\n\n    \/\/ Lock is not available.\n    var self = this;\n    if (!this._p) {\n      \/\/ Create a promise that completes when the lock is released.\n      this._p = new WinJS.Promise(function (c) { self._c = c; });\n    }\n\n    \/\/ Return a promise that waits for the lock to be released\n    \/\/ and then retries the wait.\n    return this._p.then(function () { return self.waitAsync(); });\n  },\n\n  release: function release() {\n    this._locked = false;\n    var c = this._c;\n    this._p = null;\n    this._c = null;\n\n    \/\/ Complete any promise that was waiting for release.\n    c &amp;&amp; c();\n  }\n});\n<\/pre>\n<p>Since JavaScript is single-threaded, we don&#8217;t have to worry about concurrency, so we don&#8217;t need a mutex to guard our state variables. If the lock is available, then acquire it and return a completed promise. <\/p>\n<p>Otherwise, we have to create a promise. The <code>WinJS.Promise<\/code> constructor takes a function which in turn receives a few parameters, although we use only <code>c<\/code> here. The <code>c<\/code> parameter is itself a function that our code can call to complete the promise. All we do is save that callback in the <code>_c<\/code> property so that we can complete the promise later. To avoid having to remember multiple completion callbacks, we create this promise once and cache it in the <code>_p<\/code> property. <\/p>\n<p>We then chain a continuation on that newly-created promise that restarts the <code>waitAsync<\/code>, and return the resulting promise. <\/p>\n<p>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 <code>waitAsync<\/code>, which means that each of them will race to <code>waitAsync<\/code> and retry the acquisition. <\/p>\n<p>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&#8217;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 <code>c<\/code>, and then after the lock object has been reset, we call the callback if necessary. <\/p>\n<p>We can use ES6 function arrow notation to simplify this code, but if we&#8217;re going to do that, we may as well go all the way and use ES6 native promises. <\/p>\n<pre>\nvar AwaitableLock = WinJS.Class.define(function AwaitableLock() {\n  this._locked = false;\n  this._p = null;\n  this._c = null;\n}, {\n  <font COLOR=\"blue\">waitAsync()<\/font> {\n    if (!this._locked) {\n      \/\/ Lock is available. Acquire it.\n      this._locked = true;\n      return <font COLOR=\"blue\">Promise.resolve()<\/font>;\n    }\n\n    \/\/ Lock is not available.\n    if (!this._p) {\n      \/\/ Create a promise that completes when the lock is released.\n      this._p = new <font COLOR=\"blue\">Promise<\/font>(<font COLOR=\"blue\">c =&gt; this._c = c<\/font>);\n    }\n\n    return this._p.then(<font COLOR=\"blue\">() =&gt; this.waitAsync()<\/font>);\n  },\n\n  <font COLOR=\"blue\">release()<\/font> {\n    this._locked = false;\n    var c = this._c;\n    this._p = null;\n    this._c = null;\n\n    \/\/ Complete any promise that was waiting for release.\n    c &amp;&amp; c();\n  }\n});\n<\/pre>\n<p>The ES6 Promise method for creating an already-completed promise is called <code>resolve<\/code>, as opposed to <code>wrap<\/code>, which is what <code>WinJS.Promise<\/code> calls it. The Promise constructor is the same, so we didn&#8217;t have to make any changes there aside from arrowizing the functions. Arrowizing is convenient because it preserves <code>this<\/code> inside the lambda, which saves us the trouble of having to create a separate <code>self<\/code> variable. <\/p>\n<p>And since we&#8217;re abandoning WinJS, we may as well go all the way and define the class using ES6&#8217;s native class declaration syntax. <\/p>\n<pre>\n<font COLOR=\"blue\">class AwaitableLock {<\/font>\n  <font color=\"blue\">constructor()<\/font> {\n    this._locked = false;\n    this._p = null;\n    this._c = null;\n  }\n\n  <font COLOR=\"blue\">waitAsync()<\/font> {\n    if (!this._locked) {\n      \/\/ Lock is available. Acquire it.\n      this._locked = true;\n      return Promise.resolve();\n    }\n\n    \/\/ Lock is not available.\n    if (!this._p) {\n      \/\/ Create a promise that completes when the lock is released.\n      this._p = new Promise(c =&gt; this._c = c);\n    }\n\n    return this._p.then(() =&gt; this.waitAsync());\n  }\n\n  <font COLOR=\"blue\">release()<\/font> {\n    this._locked = false;\n    var c = this._c;\n    this._p = null;\n    this._c = null;\n\n    \/\/ Complete any promise that was waiting for release.\n    c &amp;&amp; c();\n  }\n}\n<\/pre>\n<p><b>Exercise<\/b>: Why couldn&#8217;t we arrowize the constructor? <\/p>\n<p><b>Exercise 2<\/b>: Why did we set the member variables dynamically in the constructor instead of defining them statically on the prototype? <\/p>\n<p><b>Exercise 3<\/b>: Why couldn&#8217;t I have precalculated the <code>.then<\/code> of the promise? <\/p>\n<pre>\n<i>\/\/ Code in italics is wrong - but why?\nAwaitableLock.prototype.waitAsync = function waitAsync() {\n  if (!this._locked) {\n    \/\/ Lock is available. Acquire it.\n    this._locked = true;\n    return Promise.resolve();\n  }\n\n  \/\/ Lock is not available.\n  if (!this._p) {\n    \/\/ Create a promise that completes when the lock is released\n    \/\/ and then retries the wait.\n    this._p = new Promise(c =&gt; this._c = c)<font COLOR=\"blue\">.then(() =&gt; this.waitAsync())<\/font>;\n  }\n\n  return <font COLOR=\"blue\">this._p<\/font>;\n}<\/i>\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Rolling onward.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-99405","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Rolling onward.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/99405","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=99405"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/99405\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=99405"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=99405"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=99405"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}