Как создать пользовательскую .andThen() функцию цепочки для обещаний без добавления в Promise.prototype?

#javascript #ecmascript-6 #promise

#javascript #ecmascript-6 #обещание

Вопрос:

Я определил пользовательский Either тип, и функции возвращают a Promise(Either(left,right)) . Чтобы разрешить объединение таких функций в цепочки, я изменил прототип Promise следующим образом:

 Promise.prototype.andThenE = function(andThen) {
  return this.then(p => {
    return p.fold(
      e => Promise.reject(Either.left(e)),
      s => andThen(s),
    );
  }).catch(c => c);
};
  

Это позволило мне написать код, подобный:

 const response = await functionXReturningPromiseEither(input).andThenE(functionYReturningPromiseEither).andThenE(...);
  

Я знаю, что расширение прототипов собственных объектов — плохая практика, и я пытаюсь выдернуть это andThenE и поместить его прямо на себя Either , и я не могу заставить его работать / цепляться правильно.

Вот моя простая реализация Either :

 const Either = {};

Either.fromPromise = promise => {
  return promise.then(Either.right, Either.left);
};

Either.left = Either.failure = x => ({
  map: f => Either.left(x), //no-op: no mapping if "left" value
  fold: (l, r) => l(x), //apply "left" function to extract error value
  andThen: f => Either.left(x), //or flatMap or chain or join: Left(Left(x)) -> Left(x)
  dump: () => x, //for debugging
});

Either.right = Either.success = x => ({
  map: f => Either.right(f(x)), //map only if "right"
  fold: (l, r) => r(x), //apply "right" function to extract success value
  andThen: f => f(x), //or flatMap or chain or join: Right(Right(x)) -> Right(x)
  dump: () => x, //for debugging
});
  

Если я сделаю это, это будет работать безупречно:

 const f = async (arr) => Either.right(arr.map(x => x 1));

const res = await Either.fromPromise(Promise.resolve([1])).andThenE(f).andThenE(f);
console.log(res.dump());    //[3] - is of type Either.right([3])
  

Однако я не могу andThenE стать участником Either каким-либо значимым образом.

Я пытался сделать это безрезультатно:

 PromiseEither = promise => ({
  andThenE: f => {
    return PromiseEither(promise.then(p => {
      console.log("inside promise");
      return p.fold(
        e => Promise.reject(Either.left(e)),
        s => s.andThen(f),
      );
    }).catch(c => c));
  },
});

Either.fromPromise = promise => {
 PromiseEither(promise.then(Either.right, Either.left))
}
  

Это не удается:

 const res = await Either.fromPromise(Promise.resolve([1])).andThenE(f).andThenE(f);
console.log(res); //prints PromiseEither's structure and await is meaningless
  

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

Ссылка на скрипку

Ответ №1:

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

Вместо этого я бы начал с нуля, если хотите, и использовал .then в качестве точки взаимодействия между вашим типом (продолжение с другой обработкой ошибок) и promises .

Тем не менее, стандартный способ сделать то, что вы хотите, — это подкласс (гадость) или Symbol.species (еще более гадкий). Я покажу первый вариант и оставлю второй вариант в качестве упражнения для заядлого читателя:

 class PromiseEither extends Promise {
  andThenE(f) {
    return PromiseEither(this.then(p => {
      console.log("inside promise");
      return p.fold(
        e => Promise.reject(Either.left(e)),
        s => s.andThen(f),
      );
    }).catch(c => c)); // I would not do this .catch if I were you btw
  }
});

}
  

Я бы предостерег от создания подклассов promise — это очень сложно. Даже получение копии Promise из новой области и изменение прототипа (например, iframe или виртуальной машины в Node) лучше IMO.

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

1. Я надеялся, что это не связано с разделением Promise на подклассы, поскольку я думал , что это приведет к этому, но совсем не был уверен в этом TBH. Кроме того, не могли бы вы уточнить этот момент I would instead start from scratch if you want - and use .then as an interop point between your type (a continuation with different error handling) and promises. — я не уверен, что понимаю. Не могли бы вы привести пример?

2.Объект с then (чтобы вы могли его ожидать), но который использует Либо в своей базовой структуре class EitherPromiseLike extends Either , либо реализует другую часть. Также забавно, что вы позвонили flatMap andThen (поскольку я подозреваю, что «тогда» был вызван тогда по той же причине :])

3. Также, если вы не знаете / не видели — в fantasy land есть множество таких игр

4. Я возился, чтобы помочь людям, не относящимся к FP, привыкнуть к этим идеям. andThen был более интуитивным в качестве первого шага. Вы имеете в виду, что EitherPromiseLike будет иметь / переопределять then метод?