{"id":2530,"date":"2024-06-05T07:54:26","date_gmt":"2024-06-05T13:54:26","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=2530"},"modified":"2024-06-05T07:54:27","modified_gmt":"2024-06-05T13:54:27","slug":"control-javascript-promises-from-anywhere-using-promise-withresolvers","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/control-javascript-promises-from-anywhere-using-promise-withresolvers\/","title":{"rendered":"Control JavaScript Promises from Anywhere Using Promise.withResolvers()"},"content":{"rendered":"\n<p>Promises in JavaScript have always had a firm grip on their own destiny. The point at which one resolves or rejects (or, more colloquially, \u201csettles\u201d) is up to the executor function provided when the promise is constructed. A simple example:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> promise = <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Promise<\/span>(<span class=\"hljs-function\">(<span class=\"hljs-params\">resolve, reject<\/span>) =&gt;<\/span> {\n  setTimeout(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    <span class=\"hljs-keyword\">if<\/span>(<span class=\"hljs-built_in\">Math<\/span>.random() &lt; <span class=\"hljs-number\">0.5<\/span>) {\n      resolve(<span class=\"hljs-string\">\"Resolved!\"<\/span>)      \n    } <span class=\"hljs-keyword\">else<\/span> {\n      reject(<span class=\"hljs-string\">\"Rejected!\"<\/span>);\n    }\n  }, <span class=\"hljs-number\">1000<\/span>);\n});\n\npromise\n  .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">resolvedValue<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-built_in\">console<\/span>.log(resolvedValue);\n  })\n  .catch(<span class=\"hljs-function\">(<span class=\"hljs-params\">rejectedValue<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-built_in\">console<\/span>.error(rejectedValue);\n  });<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The design of this API impacts how we structure asynchronous code. If you\u2019re using a promise, you need to be OK with it owning the execution of that code.<\/p>\n\n\n\n<p>Most of the time, that model is fine. But occasionally, there are cases when it would be nice to control a promise remotely, resolving or rejecting it from\u00a0<em>outside<\/em>\u00a0the constructor. I was going to use \u201cremote detonation\u201d as a metaphor here, but hopefully your code is doing something less&#8230; destructive. So let\u2019s go with this instead: you hired an accountant to do your taxes. They could follow you around, crunching numbers as you go about your day, and they let you know when they are finished. Or, they could do it all from their office across town and ping you with the results. The latter is what I\u2019m getting at here.<\/p>\n\n\n\n<p>Typically, this sort of thing has been accomplished by reassigning variables from an outer scope and then using them when needed. Building on that example from earlier, this is what that outer scope method is like:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">let<\/span> outerResolve;\n<span class=\"hljs-keyword\">let<\/span> outerReject;\n\n<span class=\"hljs-keyword\">const<\/span> promise = <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Promise<\/span>(<span class=\"hljs-function\">(<span class=\"hljs-params\">resolve, reject<\/span>) =&gt;<\/span> {\n  outerResolve = resolve;\n  outerReject = reject;\n});\n\n<span class=\"hljs-comment\">\/\/ Settled from _outside_ the promise!<\/span>\nsetTimeout(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-built_in\">Math<\/span>.random() &lt; <span class=\"hljs-number\">0.5<\/span>) {\n    outerResolve(<span class=\"hljs-string\">\"Resolved!\"<\/span>)      \n  } <span class=\"hljs-keyword\">else<\/span> {\n    outerReject(<span class=\"hljs-string\">\"Rejected!\"<\/span>);\n  }\n}, <span class=\"hljs-number\">1000<\/span>);\n\npromise\n  .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">resolvedValue<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-built_in\">console<\/span>.log(resolvedValue);\n  })\n  .catch(<span class=\"hljs-function\">(<span class=\"hljs-params\">rejectedValue<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-built_in\">console<\/span>.error(rejectedValue);\n  });<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>It gets the job done, but it feels a little ergonomically off, particularly since we need to declare variables in a broader scope, only for them to be reassigned later on.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"a-more-flexible-way-to-settle-promises\">A More Flexible Way to Settle Promises<\/h2>\n\n\n\n<p>The new&nbsp;<code>Promise.withResolvers()<\/code>&nbsp;method makes remote promise settlement&nbsp;<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Promise\/withResolvers\">much more concise<\/a>. The method returns an object with three properties: a function for resolving, a function for rejecting, and a fresh promise. Those properties can be easily destructured and made ready for action:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table\"><mark class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> { promise, resolve, reject } = <span class=\"hljs-built_in\">Promise<\/span>.withResolvers();\n<\/span><\/mark><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>setTimeout(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-built_in\">Math<\/span>.random() &lt; <span class=\"hljs-number\">0.5<\/span>) {\n<\/span><\/span><span class='shcb-loc'><span>    resolve(<span class=\"hljs-string\">'Resolved!'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>  } <span class=\"hljs-keyword\">else<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    reject(<span class=\"hljs-string\">'Rejected!'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>}, <span class=\"hljs-number\">1000<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>promise\n<\/span><\/span><span class='shcb-loc'><span>  .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">resolvedValue<\/span>) =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-built_in\">console<\/span>.log(resolvedValue);\n<\/span><\/span><span class='shcb-loc'><span>  })\n<\/span><\/span><span class='shcb-loc'><span>  .catch(<span class=\"hljs-function\">(<span class=\"hljs-params\">rejectedValue<\/span>) =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-built_in\">console<\/span>.error(rejectedValue);\n<\/span><\/span><span class='shcb-loc'><span>  });\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Since they come from the same object, the&nbsp;<code>resolve()<\/code>&nbsp;and&nbsp;<code>reject()<\/code>&nbsp;functions are bound to that particular promise, meaning they can be called wherever you like. You\u2019re no longer tied to a constructor, and there\u2019s no need to reassign variables from a different scope.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"exploring-some-examples\">Exploring Some Examples<\/h2>\n\n\n\n<p>It\u2019s a simple feature, but one that can breathe fresh air into how you design some of your asynchronous code. Let\u2019s look at a few examples.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Slimming Down Promise Construction<\/strong><\/h3>\n\n\n\n<p>Let\u2019s say we\u2019re triggering a job managed by a web worker for some resource-heavy processing. When a job begins, we want to represent it with a promise, and then handle the outcome based on its success. To determine that outcome, we\u2019re listening for three events:\u00a0<code>message<\/code>,\u00a0<code>error<\/code>, and\u00a0<code>messageerror<\/code>. Using a traditional promise, that\u2019d mean wiring up something like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> worker = <span class=\"hljs-keyword\">new<\/span> Worker(<span class=\"hljs-string\">\"\/path\/to\/worker.js\"<\/span>);\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">triggerJob<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Promise<\/span>(<span class=\"hljs-function\">(<span class=\"hljs-params\">resolve, reject<\/span>) =&gt;<\/span> {\n      worker.postMessage(<span class=\"hljs-string\">\"begin job\"<\/span>);\n  \n      worker.addEventListener(<span class=\"hljs-string\">'message'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">e<\/span>) <\/span>{\n        resolve(e.data);\n      });\n  \n      worker.addEventListener(<span class=\"hljs-string\">'error'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">e<\/span>) <\/span>{\n         reject(e.data);\n      });\n  \n      worker.addEventListener(<span class=\"hljs-string\">'messageerror'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span>(<span class=\"hljs-params\">e<\/span>) <\/span>{\n         reject(e.data);\n      });\n  });\n}\n\ntriggerJob()\n  .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">result<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"Success!\"<\/span>);\n  })\n  .catch(<span class=\"hljs-function\">(<span class=\"hljs-params\">reason<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Failed!\"<\/span>);\n  });<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That\u2019ll work, but we\u2019re stuffing a&nbsp;<em>lot<\/em>&nbsp;into the promise itself. The code becomes a more laborious to read, and you\u2019re bloating the responsibility of the&nbsp;<code>triggerJob()<\/code>&nbsp;function (there\u2019s more than just \u201ctriggering\u201d going on here).<\/p>\n\n\n\n<p>But with&nbsp;<code>Promise.withResolvers()<\/code>&nbsp;we have more options for tidying this up:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> worker = <span class=\"hljs-keyword\">new<\/span> Worker(<span class=\"hljs-string\">\"\/path\/to\/worker.js\"<\/span>);\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">triggerJob<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  worker.postMessage(<span class=\"hljs-string\">\"begin job\"<\/span>);\n  \n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-built_in\">Promise<\/span>.withResolvers();\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">listenForCompletion<\/span>(<span class=\"hljs-params\">{ resolve, reject, promise }<\/span>) <\/span>{\n  worker.addEventListener(<span class=\"hljs-string\">'message'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">e<\/span>) <\/span>{\n    resolve(e.data);\n  });\n\n  worker.addEventListener(<span class=\"hljs-string\">'error'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">e<\/span>) <\/span>{\n     reject(e.data);\n  });\n\n  worker.addEventListener(<span class=\"hljs-string\">'messageerror'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span>(<span class=\"hljs-params\">e<\/span>) <\/span>{\n     reject(e.data);\n  });\n  \n  <span class=\"hljs-keyword\">return<\/span> promise;\n}\n\n<span class=\"hljs-keyword\">const<\/span> job = triggerJob();\n\nlistenForCompletion(job)\n  .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">result<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"Success!\"<\/span>);\n  })\n  .catch(<span class=\"hljs-function\">(<span class=\"hljs-params\">reason<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"Failed!\"<\/span>);\n  })<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This time,&nbsp;<code>triggerJob()<\/code>&nbsp;really is just&nbsp;<em>triggering the job<\/em>, and there\u2019s no constructor stuffing going on. Unit testing is likely easier too, since the functions are more narrow in purpose with fewer side effects.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Waiting for User Action<\/strong><\/h3>\n\n\n\n<p>This feature can also make handling user input more interesting. Let\u2019s say we have a\u00a0<code>&lt;dialog><\/code>\u00a0prompting a user to review a new blog comment. When the user opens the dialog, \u201capprove\u201d and \u201creject\u201d buttons appear. Without using any promises, handling those button clicks might look like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">reviewButton.addEventListener(<span class=\"hljs-string\">'click'<\/span>, () =&gt; dialog.show());\n\nrejectButton.addEventListener(<span class=\"hljs-string\">'click'<\/span>, () =&gt; {\n  <span class=\"hljs-comment\">\/\/ handle rejection<\/span>\n  dialog.close();\n});\n\napproveButton.addEventListener(<span class=\"hljs-string\">'click'<\/span>, () =&gt; {\n  <span class=\"hljs-comment\">\/\/ handle approval <\/span>\n  dialog.close();\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Again, it works. But we can centralize some of that event handling using a promise, while keeping our code relatively flat:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">const<\/span> { promise, resolve, reject } = <span class=\"hljs-built_in\">Promise<\/span>.withResolvers();\n\nreviewButton.addEventListener(<span class=\"hljs-string\">'click'<\/span>, () =&gt; dialog.show());\nrejectButton.addEventListener(<span class=\"hljs-string\">'click'<\/span>, reject);\napproveButton.addEventListener(<span class=\"hljs-string\">'click'<\/span>, resolve);\n\npromise\n  .then(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    <span class=\"hljs-comment\">\/\/ handle approval<\/span>\n  })\n  .catch(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    <span class=\"hljs-comment\">\/\/ handle rejection<\/span>\n  })\n  .finally(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    dialog.close();\n  });\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Here\u2019s how more fleshed-out implementation might look:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_rNgWbxN\" src=\"\/\/codepen.io\/anon\/embed\/rNgWbxN?height=450&amp;theme-id=47434&amp;slug-hash=rNgWbxN&amp;default-tab=js,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed rNgWbxN\" title=\"CodePen Embed rNgWbxN\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>With this change, the handlers for the user\u2019s actions don\u2019t need to be sprinkled across multiple event listeners. They can be colocated more easily, and save a bit of duplicate code too, since we can place anything that needs to run for&nbsp;<em>every<\/em>&nbsp;action in a single&nbsp;<code>.finally()<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Reducing Function Nesting<\/strong><\/h3>\n\n\n\n<p>Here&#8217;s one more example highlighting the subtle ergonomic benefit of this method. When debouncing an expensive function, it\u2019s common to see everything self-contained to that single function. There\u2019s usually no value being returned.<\/p>\n\n\n\n<p>Think of a live search form. Both the request&nbsp;<em>and<\/em>&nbsp;UI updates are likely handled in the same invocation.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">debounce<\/span>(<span class=\"hljs-params\">func<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">let<\/span> timer;\n  \n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">...args<\/span>) <\/span>{\n    clearTimeout(timer);\n    \n    timer = setTimeout(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n      func.apply(<span class=\"hljs-keyword\">this<\/span>, args);\n    }, <span class=\"hljs-number\">1000<\/span>);\n  };\n}\n\n<span class=\"hljs-keyword\">const<\/span> debouncedHandleSearch = debounce(<span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">query<\/span>) <\/span>{\n  <span class=\"hljs-comment\">\/\/ Fetch data.<\/span>\n  <span class=\"hljs-keyword\">const<\/span> results = <span class=\"hljs-keyword\">await<\/span> search(query);\n  \n  <span class=\"hljs-comment\">\/\/ Update UI.<\/span>\n  updateResultsList(results);\n});\n\ninput.addEventListener(<span class=\"hljs-string\">'keyup'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">e<\/span>) <\/span>{\n  debouncedHandleSearch(e.target.value);\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>But you might have good reason to debounce&nbsp;<em>only<\/em>&nbsp;the asynchronous request, rather than lumping the UI updates in with it.<\/p>\n\n\n\n<p>This means augmenting&nbsp;<code>debounce()<\/code>&nbsp;to return a promise that\u2019d&nbsp;<em>sometimes<\/em>&nbsp;resolve to the result (when the request is permitted to go through). It\u2019s not very different from the simpler timeout-based approach. We just need to make sure we properly resolve or reject a promise as well.<\/p>\n\n\n\n<p>Prior to&nbsp;<code>Promise.withResolvers()<\/code>&nbsp;being available, the code would\u2019ve looked very\u2026 layered:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">asyncDebounce<\/span>(<span class=\"hljs-params\">callback<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">let<\/span> timeout = <span class=\"hljs-literal\">null<\/span>;\n  <span class=\"hljs-keyword\">let<\/span> reject = <span class=\"hljs-literal\">null<\/span>;\n\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">...args<\/span>) <\/span>{\n    reject?.(<span class=\"hljs-string\">'rejected_pending'<\/span>);\n    clearTimeout(timeout);\n    \n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Promise<\/span>(<span class=\"hljs-function\">(<span class=\"hljs-params\">res, rej<\/span>) =&gt;<\/span> {\n      reject = rej;   \n      \n      timeout = setTimeout(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n        res(callback.apply(<span class=\"hljs-keyword\">this<\/span>, args));\n      }, <span class=\"hljs-number\">500<\/span>);\n    });\n  };\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That\u2019s a dizzying amount of function nesting. We have a function that returns a function, which constructs a promise accepting a function containing a timer, which takes another function. And only in&nbsp;<em>that<\/em>&nbsp;function can we call the resolver, finally invoking the function provided like 47 functions ago.<\/p>\n\n\n\n<p>But now, we could streamline things at least a&nbsp;<em>little<\/em>&nbsp;bit:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">asyncDebounce<\/span>(<span class=\"hljs-params\">callback<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">let<\/span> timeout = <span class=\"hljs-literal\">null<\/span>;\n  <span class=\"hljs-keyword\">let<\/span> resolve, reject, promise;\n\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">...args<\/span>) <\/span>{\n    reject?.(<span class=\"hljs-string\">'rejected_pending'<\/span>);\n    clearTimeout(timeout);\n\n    ({ promise, resolve, reject } = <span class=\"hljs-built_in\">Promise<\/span>.withResolvers());\n    \n    timeout = setTimeout(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n      resolve(callback.apply(<span class=\"hljs-keyword\">this<\/span>, args));\n    }, <span class=\"hljs-number\">500<\/span>);\n\n    <span class=\"hljs-keyword\">return<\/span> promise;\n  };\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Updating the UI while discarding the rejected invocations could then look something like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">input.addEventListener(<span class=\"hljs-string\">'keyup'<\/span>, <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">e<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> results = <span class=\"hljs-keyword\">await<\/span> debouncedSearch(e.target.value);\n\n    appendResults(results);\n  } <span class=\"hljs-keyword\">catch<\/span> (e) {\n    <span class=\"hljs-comment\">\/\/ Discard exceptions from intentionally rejected<\/span>\n    <span class=\"hljs-comment\">\/\/ promises, but let everything else throw.<\/span>\n    <span class=\"hljs-keyword\">if<\/span>(e !== <span class=\"hljs-string\">'rejected_pending'<\/span>) {\n      <span class=\"hljs-keyword\">throw<\/span> e;\n    }\n  }\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>And we\u2019d get the same desired experience, without bundling everything up into a single&nbsp;<code>void<\/code>&nbsp;function:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_vYwgmRP\" src=\"\/\/codepen.io\/anon\/embed\/vYwgmRP?height=450&amp;theme-id=47434&amp;slug-hash=vYwgmRP&amp;default-tab=js,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed vYwgmRP\" title=\"CodePen Embed vYwgmRP\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>It\u2019s not a dramatic change, but one that smooths over some of the rough edges in accomplishing such a task.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"a-tool-for-keeping-more-options-open\">A Tool for Keeping More Options Open<\/h2>\n\n\n\n<p>As you can see, there\u2019s nothing conceptually groundbreaking introduced with this feature. Instead, it\u2019s one of those \u201cquality of life\u201d improvements. Something to ease the occasional annoyance in architecting asynchronous code. Even so, I\u2019m surprised by how frequently I\u2019m beginning to see more use cases for this tool in my day-to-day, along with many of the other&nbsp;<a href=\"https:\/\/tc39.es\/ecma262\/multipage\/control-abstraction-objects.html#sec-properties-of-the-promise-constructor\">Promise properties<\/a>&nbsp;introduced in the past few years.<\/p>\n\n\n\n<p>If anything, I think it all verifies how foundational and valuable Promise-based, asynchronous development has become, whether it\u2019s run in the browser or on a server. I\u2019m eager to see how much we can continue to level-up the concept and its surrounding APIs in the future.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This method enhances flexibility by allowing promises to be resolved or rejected remotely, simplifying and streamlining asynchronous code.<\/p>\n","protected":false},"author":25,"featured_media":2534,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"Control JavaScript Promises from Anywhere Using withResolvers()","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[3,185],"class_list":["post-2530","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-javascript","tag-promises"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/06\/cHJpdmF0ZS9zdGF0aWMvaW1hZ2Uvd2Vic2l0ZS8yMDIyLTA0L2xyL3B4OTczMjMzLWltYWdlLWt3dnVvZzUzLmpwZw.webp?fit=1024%2C683&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2530","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/25"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=2530"}],"version-history":[{"count":6,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2530\/revisions"}],"predecessor-version":[{"id":2543,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/2530\/revisions\/2543"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/2534"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=2530"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=2530"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=2530"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}