управляем промисами из любой точки кода
До внедрения Promise.withResolvers() существовал только один способ создания промисов напрямую:
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
В большинстве случаев дизайн этого API вполне приемлем, но все же он сильно ограничивает структуру нашего асинхронного кода. Это можно проиллюстрировать на примере, в котором мы попытаемся зарезолвить* и отклонить промис извне конструктора:
let globalResolve, globalReject;
const promise = new Promise((res, rej) => {
globalResolve = res;
globalReject = rej;
});
Math.random() > 0.5 ? globalResolve("Resolved") : globalReject("Rejected");
* - здесь и далее я буду использовать cлово "зарезолвить" вместо "разрешить" - просто в жизни я говорю именно так 😅
Да, это работает, но выглядит немного неудобно, особенно потому, что нам нужно объявлять переменные в более широкой области, только чтобы затем переназначить их.
Новый метод Promise.withResolvers() делает удаленный резолв промисов более лаконичным. Метод возвращает объект, в котором находятся: функция для резолва, функция для отклонения и сам промис. Этот объект можно деструктурировать:
const { promise, resolve, reject } = Promise.withResolvers();
Math.random() > 0.5 ? resolve("Resolved") : reject("Rejected");
Так как они происходят из одного объекта, функции resolve() и reject() привязаны к конкретному промису, что означает, что их можно вызывать где угодно. Больше нет необходимости привязываться к конструктору или переназначать переменные из другой области.
Самое очевидное приимущество withResolvers в том, что он помогает избежать вложенности при создании промиса и уменьшить объем шаблонного кода.
Но также полезно его использовать в случае, когда вам нужно создать несколько эвентов с resolve/reject. Посмотрите на этот пример создания класса EventsAgregator. Он имеет метод add для добавления нового события и метод abort, который отменяет агрегацию. Но самое главное то, что он возвращает промис событий, который резолвится, когда достигает лимита eventsCount, или отклоняется, когда запускается abort.
class EventsAggregator {
constructor(eventsCount) {
this.eventsCount = eventsCount;
this.events = [];
// Object.assign(this, withResolvers()) -> this.promise, this.resolve, this.reject
Object.assign(this, withResolvers());
}
addEvent(event) {
if (this.events.length < this.eventsCount) {
this.events.push(event);
}
if (this.events.length === this.eventsCount) {
this.resolve(this.events);
}
}
abort() {
this.reject("Aborted.");
}
get eventsPromise() {
return this.promise;
}
}
const eventsAggregator = new EventsAggregator(3);
eventsAggregator.eventsPromise
.then((events) => console.log("Resolved: ", events))
.catch((reason) => console.error("Rejected: ", reason));
eventsAggregator.addEvent("first");
eventsAggregator.addEvent("second");
eventsAggregator.addEvent("third");
// Resolved: [ "first", "second", "third" ]
Если мы завершим агрегацию событий, вызвав метод abort, промис будет отклонен:
eventsAggregator.addEvent("first");
eventsAggregator.abort();
eventsAggregator.addEvent("second");
eventsAggregator.addEvent("third");
// Rejected: Aborted.
WithResolvers появился в спецификации совсем недавно, поэтому некоторые среды могут не поддерживать его. Вот как реализовать его самостоятельно:
function withResolvers() {
let resolve, reject;
let promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
И ванильные промисы, и Promise.withResolvers() — это удобные инструменты для работы с ассинхронным кодом в JS. В то время как традиционные промисы обеспечивают надежную функциональность, Promise.withResolvers() предлагает более лаконичный способ обработки промисов.