Сделать ожидание возврата доступным или бесконечную рекурсию в ожидающем ожидаемом

Попытка придумать API, смешивая такие функции обещаний, как:

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 был вновь созданным доступным экземпляром.

Заглушка then на null делает ожидаемый результат неполным.

Интересно, возможно ли это вообще, кажется, есть какое-то концептуальное препятствие, я не могу уложиться в голове.


person dy_    schedule 10.09.2019    source источник
comment
Нет, вы не можете разрешить промис сам с собой.   -  person Bergi    schedule 11.09.2019
comment
Вроде не только с собой, а вообще со всякой лабудой. Есть ли обходной путь?   -  person dy_    schedule 11.09.2019
comment
Какова ваша основная идея? Что вы пытаетесь сделать здесь? Зачем вам нужен класс Awaitable? Вы же не хотите создавать рекурсию, верно?   -  person Bergi    schedule 11.09.2019
comment
Представьте, что jQuery будет ожидаемым и вернет набор, когда все эффекты будут выполнены let $target = await $(target).fadeIn(100)   -  person dy_    schedule 11.09.2019
comment
Оболочка jQuery на самом деле является ожидаемой, она просто вызывает дополнительный метод: .promise()< /а>. Но полученное обещание выполняется с undefined afaik, а не с самой оболочкой jQuery.   -  person Bergi    schedule 11.09.2019
comment
Это хороший обходной путь, но я надеялся найти доказательство того, что первоначальный или аналогичный API невозможен. Я считаю, что это может быть полезным шаблоном для организации API.   -  person dy_    schedule 11.09.2019
comment
Да, TY, дело не в jQuery, а в паттерне организации таких API - не 100%, что путь jQuery является стандартом.   -  person dy_    schedule 11.09.2019
comment
Трудно понять, чего вы пытаетесь достичь, чего еще нет в промисах с асинхронностью/ожиданием или без них.   -  person Roamer-1888    schedule 11.09.2019


Ответы (2)


Обещание (или любое другое) не имеет смысла выполнять с самим собой. Если бы у вас уже был объект, вам не нужно было бы ждать его.

Разрешение обещания автоматически разворачивает затемняемые элементы, что в вашем случае создаст бесконечную рекурсию, где оно разрешится само по себе, и вы может (к сожалению) не избежать этого. Так что просто не пытайтесь это сделать. Вместо этого выполните свое обещание ни с чем (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);
person Bergi    schedule 10.09.2019
comment
Да, но что достигается из того, что еще не доступно const smth = Promise.resolve(undefined); обычным способом? Может быть, если бы ОП мог объяснить шаблон для организации таких API .... - person Roamer-1888; 11.09.2019
comment
@ Roamer-1888 Я ожидаю, что эта штука будет обертывать более сложное обещание и будет иметь больше методов, чем просто then. В противном случае да, можно было бы просто использовать простое обещание. - person Bergi; 11.09.2019
comment
@ Roamer-1888 под шаблоном я имею в виду let s = await (new Something()).asyncA().asyncB() - это очень удобно для организации очередей задач для каждого экземпляра. Это может быть что угодно — от соединителей баз данных до внутренних API. В частности, я реализую это на github.com/spectjs/spect. Я думаю, что попробую динамический метод then - если что-то запланировано, экземпляр можно использовать, иначе нет - ожидание не вызывает рекурсию таким образом. Я постараюсь придумать ответ. - person dy_; 11.09.2019
comment
@dy_, помимо того, что вы здесь спрашиваете, вас может заинтересовать Спецификация Fantasy Land. - person Roamer-1888; 11.09.2019

Правильное решение

Предложение 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

Хрупкое нестандартное решение

Обнаружение того, вызывается ли экземпляр 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, чтобы закрепиться - для этого потребуется своего рода волшебство статического анализа.

person dy_    schedule 11.09.2019