使 await return thenable,或者等待 awaitable 中的无限递归

Make await return thenable, or infinite recursion in awaiting awaitable

尝试想出 API,混合 promise 功能,例如:

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)

此代码创建递归。 重点是让smth成为新创建的thenable实例。

thennull 打桩会使等待的结果不完整。

我想知道这是否可能,似乎有一些概念上的障碍,我无法理解。

承诺(或任何 thenable)自行实现是没有意义的。如果您已经拥有该对象,则无需等待。

, which would create infinite recursion in your case where it would resolve with itself, and you 。所以不要尝试这样做。兑现你的诺言,什么都不做 (undefined)。

class Awaitable {
  constructor () {
    this.promise = Promise.resolve(undefined); // simplified
  }
  then(onfulfill, onreject) {
    return this.promise.then(onfulfill, onreject);
  }
  // other methods
}

const smth = new Awaitable();
await smth; // works just fine now
console.log(smth);

正确的解决方案

Symbol.thenable提案。

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

脆弱的non-standard解决方案

通过解析调用堆栈可以检测 thenable 实例是否被 await 调用。这是基于 stacktrace-parser 包的解决方案:

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

必须为 FF/nodejs 增强启发式以巩固 - 这需要某种静态分析技巧。