Выполнение обещаний и их использование в порядке их разрешения.

Идеи

  • Когда выполняется несколько асинхронных вызовов, чтобы иметь возможность начать потребление с первого разрешающего, независимо от того, в каком порядке запускаются обещания.
  • Чтобы создать современный эмиттер асинхронных значений, который будет использоваться for await петля.
  • Этого можно добиться, повторяя (много раз обещайте) Promise.race()s, но я хочу верить, что ни один хороший программист не почувствует себя комфортно с этим.
  • Потому что это расточительно, и мне никогда не нравилось Promise.race(). Это неправильное название. Я имею в виду, где в мире есть гонка, которая завершается, когда кто-то выигрывает? Это оскорбление. Это все равно, что назвать вторую «Эй .. ты на вершине всех неудачников». Давай изменим это.

Одной из реализаций этой функции может быть начало рендеринга составной сущности раньше, чем в противном случае. Могут быть и другие варианты использования, так как это не будет отвергнуто полностью, даже если обещание в системе каким-то образом будет отклонено. Мы можем легко обрабатывать отказы изолированно, по отдельности, возможно, для повторной попытки или еще чего-то. Также обещания могут быть идентифицированы по мере их выполнения, и мы можем сказать, где использовать их разрешение.

Код ниже — это просто скелет.

  • Он просто показывает механизм с простой обработкой исключений.
  • Он не включает возможности повторной попытки или обещания идентификации.
  • Он не удаляет из структуры использованное (выполненное) обещание. Это можно добавить в finally блокировать. После его реализации count переменная будет избыточной, и тогда мы сможем использовать динамический length свойство вместо этого.
  • Возможность использовать динамический length свойство позволит нам добавлять, удалять или заменять обещания в любой момент времени, просто используя стандартные методы массива, такие как .push() или же .splice() и т.п.
  • Он использует новый Поля частного класса что мне очень нравится.

Мы начнем с нового типа данных, который называется SortedPromisesArray который на самом деле является продолжением Array тип. Мы добавим в него генератор и asyncIterator способности.

export default class SortedPromisesArray extends Array {
  #resolve;
  #reject;
  #count;
  constructor(...args){
    super(...args.filter(p => Object(p).constructor === Promise)); // Make sure everybody is a Promise
    this.#count = this.length;
    this.forEach(p => p.then(v => this.#resolve(v), e => this.#reject(e)));
  };
  async *[Symbol.asyncIterator]() {
    while(this.#count--) {
      try {
        yield new Promise((...rs) => [this.#resolve,this.#reject] = rs);
      }
      catch(e){
        console.log(`Caught an exception ${e}`);
      }
      finally{
        // a handy stage to do useful things
      };
    };
  };
};

и мы можем потреблять это как;

import SPA from "../lib/promise-sort.js";

var promise  = (val,delay,isOK) => new Promise((v,x) => setTimeout(_ => isOK ? v(val) : x(val), delay)),
    promises = [ promise("Third", 3000, true)
               , promise("First", 1000, true)
               , Promise.resolve("this is solved first @ microtask queue")
               , promise("Second", 2000, false) // NOTE: this one rejects!
               ],
    sortedPS = new SPA(...promises);

async function sink() {
  for await (let value of sortedPS){
      console.log(`Got: ${value}`);
  };
};

sink();

Любые мысли и рекомендации приветствуются.

1 ответ
1

вопросы

Утраченное разрешение

Вы даете обещания без гарантии того, что сможете выполнить все или некоторые из решающихся обещаний.

Это произойдет, если обещания разрешатся до того, как вы начнете for await петля.

Оскорбительный код находится в строке this.forEach(p => p.then(v => this.#resolve(v), e => this.#reject(e)));

Призывы к this.#resolve а также this.#reject требовать, чтобы asyncIterator запускается так, чтобы this.#resolve а также this.#reject назначены функции. В противном случае они не определены, когда обещание разрешается.

Также возможно, что обещание перезапишет разрешенное значение предыдущего обещания без того, чтобы итератор увидел это значение.

Например

  • если вы отложите звонок на sink(); с участием setTimeout(sink, 4000) ни одно из обещаний не обрабатывается в функции sink. Результаты теряются и не могут быть восстановлены

  • Если итератор ожидает содержимого, которое позволяет разрешить более одного из повторяемых обещаний между итерациями.

    Пример: следующая функция теряет второе обещание. (с учетом ваших тестовых данных)

async function sink() {
    const p = new Promise(v=>setTimeout(v, 1200, "zz"))
    for await (const value of sortedPS){
       log(value);
       log(await p);
    };
};

Заглушить ошибки!

Никогда не заглушайте ошибки, за которые вы не несете ответственности.

Вы делаете это в очереди

super(...args.filter(p => Object(p).constructor === Promise)); // Make sure everybody is a Promise

Эфир выдает ошибку или позволяет ошибке возникать естественным образом. Если ваш код не может правильно работать с данными аргументами, бросьте

Пример проверки и выброса ошибки

constructor(...promises){
    super(...promises);
    if (promises.some(promise => !(promise instanceof Promise))) {
        throw new RangeError("Array can only contain Promises");
    }

Плохое наследство

Вы расширяете Array, но забываете обрабатывать какие-либо унаследованные функции или свойства.

Например, можно было бы ожидать, что SortedPromisesArray.push добавит обещание, которое разрешится. В его нынешнем виде добавленные обещания игнорируются.

Простой пример расширения унаследованной функции

  push(...promises) {
      if (promises.some(promise => !(promise instanceof Promise))) {
          throw new RangeError("Array can only contain Promises");
      }      
      this.#count += promises.length;
      super.push(...promises);
      promises.forEach(p => p.then(v => this.#resolve(v), e => this.#reject(e)));
  }

То же самое применимо к любой из унаследованных функций, которые изменяют содержимое массивов.

Массивы не зависят от содержимого

Однако остается проблема поведения, которую нельзя расширить без использования прокси. Например sortedPS[sortedPS.length] = new Promise(...)

Без прокси вы останетесь с объектом, который ведет себя непоследовательно.

Поэтому не рекомендуется расширять массивы, если поведение расширенного массива зависит от содержимого массива.

Обновлять

Я добавил пример RaceAll как фабрика, которая создает асинхронный итерируемый объект, который гоняет массив обещаний в порядке разрешения.

Это достигается путем пометки обещаний идентификаторами и использования идентификаторов разрешенных обещаний для их удаления из массива гонки.

Он не включает в себя проверку и не разрешает отклоненные / невыполненные обещания (оба могут быть легко реализованы).

Это гарантирует, что никакие обещания не будут потеряны, и не требует повторения перед выполнением любых обещаний.

function RaceAll(...promises) {
    var racing = promises.map((promise, id) => Object.assign(
            new Promise(r => promise.then(result => r({id, result}))), {id}
        ));
    return Object.freeze(Object.assign([], {
            async *[Symbol.asyncIterator]() {
                while (racing.length) {
                    const resolved = await Promise.race(racing);
                    yield resolved.result;
                    racing = racing.filter(r => r.id !== resolved.id);
                }
            }
        }));
}

  • Спасибо. Есть моменты, с которыми я бы согласился и поспорил. В Утраченное разрешение Раздел приемлемый, но я думаю, что это немного хм. В .sink() функциональность уже должна быть там, и больше ничего awaitв контексте тоже. Вы можете рассматривать все это как Promise.race () `, из которого вы не можете вмешиваться во внутреннюю работу. Тем не менее, это дало мне идею, как это исправить. пожалуйста, проверьте это и на панели оболочки запустите его как ~/promise-race$ deno run ./promise-sort/test/promise-sort-test.js.

    — Все в порядке


  • OTH да, я согласен с тем, что Array не является идеальным типом данных для этого. Возможно, было бы лучше использовать связанный список или аналогичный. Не могли бы вы написать мне письмо, которое есть в моем профиле и которое легко расшифровать. 🙂 Я просто не хочу загрязнять комментарии.

    — Все в порядке

  • @Redu Извините, я ограничиваю общение только комментариями и ответами

    — Слепой67

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *