Search code examples
javascriptpromiseasync-awaites6-promiseecmascript-2017

Make await return thenable, or infinite recursion in awaiting awaitable


Trying to come up with API, mixing in promise functionality like:

class Awaitable {
  constructor () {
    this.promise = Promise.resolve()
  }
  then (fn) {
    // awaited result must be _this_ instance
    return this.promise.then(() => fn(this))
  }
}

let smth = await (new Awaitable())
console.log(smth)

This code creates recursion. The main point is to have smth to be the newly created thenable instance.

Stubbing then with null makes awaited result incomplete.

I wonder if that's possible at all, seems like there's some conceptual hurdle, I can't wrap my head around.


Solution

  • The correct solution

    Symbol.thenable proposal.

    import { parse as parseStack } from 'stacktrace-parser' 
    
    class Awaitable {
      constructor () {
        this.promise = Promise.resolve()
      },
    
      [Symbol.thenable]: false,
    
      then(fn) {
        this.promise.then(() => fn(this))
        return this
      }
    }
    
    let smth = await (new Awaitable())
    console.log(smth.then) // function

    Fragile non-standard solution

    Detecting if thenable instance is called by await is possible by parsing callstack. Here's solution based on stacktrace-parser package:

    import { parse as parseStack } from 'stacktrace-parser' 
    
    class Awaitable {
      constructor () {
        this.promise = Promise.resolve()
      }
    }
    
    Object.defineProperty(Awaitable.prototype, 'then', {
      get() {
        let stack = parseStack((new Error).stack)
        
        // naive criteria: if stacktrace is leq 3, that's async recursion, bail out
        // works in webkit, FF/nodejs needs better heuristic
        if (stack.length <= 3) return null
        
        return (fn) => {
          this.promise.then(() => {
            fn(this)
          })
          return this
        }
      }
    })
    
    let smth = await (new Awaitable())
    console.log(smth.then) // function

    The heuristic must be enhanced for FF/nodejs, to solidify - that'd require sort of static analysis wizardy.