Является ли наблюдаемое из цепных обещаний эквивалентом наблюдаемых, созданных с помощью и связанных с помощью конкатмапа?

#promise #rxjs #observable #dexie

Вопрос:

У меня есть угловое приложение, в котором есть хранилище DexieDb / IndexedDB для объектов, выбранных с карты. Я всегда очищаю базу данных перед добавлением новых элементов. Если новые элементы одинаковы, иногда может возникнуть ошибка ограничения:

selectionItemInfos.bulkaddd(): 1 из 1 операций не удалась. Ошибки: ConstraintError: Ключ уже существует в хранилище объектов.

Ошибка может быть воспроизведена путем повторного вызова метода ниже (в приложении как можно быстрее дважды щелкните элемент).:

   public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
    return from(this.db.selectionItemInfos.clear().then(() => {
      return this.db.selectionItemInfos.bulkAdd(itemInfos);
    }));
  }
 

Однако, если я изменю реализацию на:

   public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
    const clear$ = from(this.db.selectionItemInfos.clear());
    const bulkAdd$ = from(this.db.selectionItemInfos.bulkAdd(itemInfos));

    return clear$.pipe(concatMap(() => bulkAdd$))
  }
 

Я не смог воспроизвести его.

Интересно, есть ли что-то не так с 1-й реализацией / я ошибочно предположил, что таблица очищается при вызове bulkAdd ИЛИ что-то не так с реализацией DexieDb / IndexedDB (а именно, что она возвращается до того, как она действительно очищена)?

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

Комментарии:

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

2. вы имеете в виду Наблюдаемые, а не Обещания? В 1-м примере есть только 1 наблюдаемый, а во 2-м-2 наблюдаемых.

Ответ №1:

Во-первых, я удивлен, что второй пример не является тем, который приводит к ошибке.

 const clear$ = from(this.db.selectionItemInfos.clear());
const bulkAdd$ = from(this.db.selectionItemInfos.bulkAdd(itemInfos));
return clear$.pipe(concatMap(() => bulkAdd$))
 

То, что вы завернули обещание в «от«, не делает его отложенным. Поэтому в этом случае clear() и bulkaddd() должны начать выполняться немедленно и асинхронно, до настройки потока.

Первая версия лучше, так как два вызова должны выполняться последовательно, но, конечно, могут завершиться неудачей, если они выполняются в быстрой последовательности. Это связано с тем, что у вас может быть два вызова clear() execute, а затем два вызова blukAdd (), поскольку вызовы setItemInfos (), вероятно, не будут ожидаться.

Таким образом, оба способа неверны, и единственная причина, по которой я мог догадаться, почему second менее подвержен ошибкам, заключается в том, что bulkAdd() действительно должен ждать, пока clear() не будет полностью выполнен, и проблема заключается в том, что у bulkaddd () просто два последовательных выполнения.

Способ заставить вашу первую реализацию работать без ошибок-использовать concactMap() или switchMap() для предотвращения нескольких одновременных исполнений. Разница между ними заключается в том, что последнее отменит предыдущие выполнения, когда следующий результат будет получен из потока более высокого уровня.

 readonly setItemInfoSubject = new Subject<IItemInfo[]>();

private readonly doSetItemInfoSubcription = this.setItemInfoSubject.pipe(
  switchMap(itemInfos => this.db.selectionItemInfos.clear()
    .then(() = this.db.selectionItemInfos.bulkAdd(itemInfos)))
).subscribe();
 

Наконец, во втором примере не было особого смысла оборачивать второе обещание с помощью from, поскольку оно использовалось внутри оператора, который принимает ObservableInput. Псевдоним этого типа сопоставляется со следующими типами: Наблюдаемый, наблюдаемый, асинхронный, многообещающий, похожий на массив, Повторяющийся, читаемый поток.

Комментарии:

1. Ожидание обещания несколько раз просто дает вам обналиченную стоимость в обещании. Это когда-нибудь разрешится только один раз. Первый подход каждый раз порождает новое обещание, второй-нет. Это объясняет, почему второй не ошибается (но, скорее всего, он делает не то, что хочет автор).

2. setItemInfos(x).подписка вызывается каждый раз при вызове обработчика двойного клика. я думал, что обертывание обещания для наблюдаемого означает, что оно ведет себя как наблюдаемое

3. @charm Если вы вызовете функцию this.db.selectionItemInfos.clear() , то есть , она немедленно начнет ее выполнять — независимо от того, что делает функция, которой вы передаете результат. Ответ, как всегда, когда вы хотите отложить выполнение, заключается в том, чтобы вместо этого предоставить функцию. Проверьте отложенный , где вы можете завершить вызов функции, производящей обещание.

4. В тебе есть это очарование. Я действительно отвык от привычки относиться к наблюдаемым как к обещаниям и возвращать новое. Это приводит меня в ситуации, подобные той, в которую ты попал. В вашем случае у вас действительно есть обещание, поэтому я не вижу смысла в создании наблюдаемого, ЕСЛИ вы не хотите воспользоваться некоторыми дополнительными функциями rxj, такими как concatMap или switchMap, и их способностью предотвращать одновременное выполнение.

5. Тогда не отказывайся от своих обещаний — все в порядке. Просто я так поступаю, и мне это нравится. Что вам нужно сделать, так это использовать карту переключения или карту объединения на более высоком уровне, если вы хотите, чтобы эти ошибки исчезли.