returning non-Promise values from async functions and running them synchronously (or Promise.sync() idea)

Jordan Harband ljharb at gmail.com
Mon Feb 4 06:42:33 UTC 2019


Typically, APIs that are sometimes sync and sometimes async are called
"z̲̗̼͙̥͚͛͑̏a̦̟̳͋̄̅ͬ̌͒͟ļ̟̉͌ͪ͌̃̚g͔͇̯̜ͬ̒́o̢̹ͧͥͪͬ" - unpredictable, hard to
maintain, hard to understand. The general best practice is that a function
should always be async, or always sync, but never the twain shall meet.

Relevant:  http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony

On Sun, Feb 3, 2019 at 4:49 PM Isiah Meadows <isiahmeadows at gmail.com> wrote:

> You could move the async part into a helper function and call that from a
> sync function you instead expose. I've used this trick more than once, and
> although it is a bit of boilerplate, it works well enough.
> On Sun, Feb 3, 2019 at 18:40 #!/JoePea <joe at trusktr.io> wrote:
>
>> I often find myself enjoying async functions until the time comes when I
>> don't want to await anything, and I want the call stack to be sync (i.e. I
>> don't want to defer if I don't have to).
>>
>> But, async functions always return a Promise. So I find my self
>> converting my async functions back into normal functions so that I can
>> return non-Promise values, this way the caller can wait only if I return a
>> promise, otherwise continue synchronously.
>>
>> Here's an example function in one of my classes:
>>
>> ```js
>>             initWebGL() {
>>                 if (this.__glLoading) return false
>>                 this.__glLoading = true
>>
>>                 // order of the if-else matters!
>>                 if (this.__glUnloading) return
>> Promise.resolve().then(A.bind(this))
>>                 else if (this.__glLoaded) return false
>>
>>                 A.call(this)
>>
>>                 function A() {
>>                     // ... load stuff (omitted for brevity) ...
>>                 }
>>
>>                 return true
>>             }
>> ```
>>
>> then the caller (f.e. a subclass) only needs to wait when a promise is
>> returned:
>>
>> ```js
>>         initWebGL() {
>>             const superResult = super.initWebGL()
>>             if (superResult instanceof Promise) return
>> superResult.then(A.bind(this))
>>             if (!superResult) return false
>>
>>             A.call(this)
>>
>>             function A() {
>>                     // ... subclass loads stuff (omitted for brevity) ...
>>             }
>>
>>             return true
>>         }
>> ```
>>
>> This is just one example, but in general I find many cases where I want
>> to run synchronously most of the time, and only sometimes have to defer
>> (whether by choice or not).
>>
>> So I am losing out on the nice `await` syntax just because I can not do
>> what I want to do with `async`/`await`.
>>
>> What if async functions could have some way to continue synchronously,
>> and return non-Promise values?
>>
>> Or, what if we could have some sort of Promise API that would cause any
>> callers to synchronously away, like in the following example, so that the
>> change is not a breaking change, and async functions would still return
>> Promises but the Promise could perhaps continue synchronously:
>>
>> ```js
>> async function example() {
>>   return Promise.sync()
>> }
>> ```
>>
>> Then, in a caller, there would be no microtask deferral:
>>
>> ```js
>> async function test() {
>>   await example()
>>   console.log('one')
>>   // implicit return is a Promise.sync() here.
>> }
>>
>> function main() {
>>   test()
>>   console.log('two')
>> }
>> ```
>>
>> The output in this case would be:
>>
>> ```
>> "one"
>> "two"
>> ```
>>
>> Note that in that example, we are _sure_ that `two` is logged after `one`
>> because those async functions only ever use `Promise.sync()`.
>>
>> It would be good practice to use `await` regardless, because one may not
>> know if the async function they are calling will return a
>> non-`Promise.sync` value. So the `main` function is better written as
>>
>> ```js
>> async function main() {
>>   await test()
>>   console.log('two')
>> }
>> ```
>>
>> In this case, everything still happens synchronously, but if the author
>> of `example()` changes the implementation to return a non-sync Promise,
>> then things will still run in the expected order. F.e.
>>
>> ```js
>> async function example() {
>>   await fetch(...)
>> }
>> ```
>>
>> I keep finding scenarios where I want to avoid async unless I need async,
>> but then I lose the convenient async/await syntax.
>> _______________________________________________
>> es-discuss mailing list
>> es-discuss at mozilla.org
>> https://mail.mozilla.org/listinfo/es-discuss
>>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20190203/4bf966d7/attachment-0001.html>


More information about the es-discuss mailing list