Thenable-трюки — 13 мая 2026 г. в 06:29:55.475
Thenable-трюки Когда в стандарте JS появились нативные промисы, уже существовало множество библиотек, реализующих похожий подход. И перед разработчиками стандарта встал вопрос: что делать со всем этим многообразием? Реши они тогда, что единственно верное решение — просто переписать всё со старого на новое, процесс перехода на промисы затянулся бы на очень долгое время. Но они решили иначе и ввели в стандарт понятие Thenable-объектов для обратной совместимости с Promise. Например: const bob = await { then(cb) { cb("bob"); } }; console.log(bob); // bob Теперь старые библиотеки могут бесшовно использоваться вместе с Promise. Все, можно расходиться? Не торопитесь. Кто хоть раз был на моих лекциях, знает, что я никогда не делаю банальные материалы. И вот тут ситуация не исключение. Давайте на секунду задумаемся: какие ещё возможности открывает нам такая совместимость? Мы можем делать не просто альтернативную реализацию промисов, а API с совершенно иной семантикой вычисления. Привести пример? Легко. Давайте сделаем контейнер для ленивых вычислений. class Lazy { #task; #value; #state = 0; constructor(task) { this.#task = task; } bind(cb) { return new Lazy(() => cb(this.unwrap())); } unwrap() { if (this.#state === 0) { this.#value = this.#task(); this.#state = 1; } return this.#value; } then(cb) { return Promise.try(() => this.bind(cb).unwrap()); } } Используя этот контейнер, мы можем создавать вычисления, которые не будут выполнены, пока кто-нибудь явно этого не попросит. Но когда это произойдёт, результат закешируется. Более того, используя монадический bind, мы можем строить цепочки таких ленивых вычислений. Представьте ситуацию: у вас есть функция, которая инициализирует конфиг из IndexedDB, или создаёт соединение с сервером, или загружает ресурсы для компонента, который спрятан за клик. Короче говоря, делает то, что не хочется выполнять сразу, а только тогда, когда появится реальная необходимость. Итак, у нас есть код, который полагается на то, что эти вычисления должны быть сделаны. Чтобы «пнуть» контейнер и заставить его вычислить (или вернуть результат), нужно вызвать unwrap. Но в дополнение результат вычисления может быть промисом, так как сама природа Lazy предполагает, что мы откладываем на потом всякие асинхронные штуки. Тогда пользователю пришлось бы написать что-то в духе: await data.unwrap() // Или, если unwrap не вернёт Promise или выбросит исключение: Promise.try(() => data.unwrap()).then(...) Но мы с вами сделали этот объект совместимым с Thenable — поэтому все эти телодвижения нам просто не нужны! await data И что здесь прекрасно: использующая этот объект сторона вообще не знает о природе и семантике этого объекта. Для неё это просто промис, и она работает с ним соответствующим образом. А мы тем временем создали объект совершенно для другой задачи. Красота! И примеров таких контейнеров — масса. Всё ограничивается лишь вашим опытом и фантазией. Про такие (и не только) нестандартные способы использования промисов я буду говорить на своём новом интенсиве по асинхронному программированию. Мы не только разберём от А до Я все необходимые для работы примитивы, но и научимся использовать их так, как вы даже подумать не могли. Будет не банально, интенсивно, глубоко — и невероятно. Жду тебя! 🔥 [ Присоединиться к интентенсиву ] 🚀 Стартуем уже через 2 дня

