Распространенные ошибки при использование Promise в Javascript
Если вы постоянно работаете с JavaScript, вы, вероятно, уже хорошо знаете что такое Promise в ES6. Promise предоставляют чистый, гибкий способ объединить несколько асинхронных операций без необходимости использования функций обратного вызова. Promise могут быть очень полезны, но только если вы не будете допускать следующие ошибки в коде. Все описанные ниже ошибки формально не являются ошибками, но они значительно влияют на производительность кода.
Спонсор поста Карта знаний JS-Frontend программиста
Детализированная карта Знаний JavaScript Frontend программиста от Trainee до Senior. Используйте для самообучения, поиска пробелов в знаниях, создания программ обучения, подготовки к собеседованиям и продвижениям по карьерной лестнице.
Поэтому в этой статье я хотел бы поговорить об распространенных ошибках, которые многие допускают или обычно игнорируют. А также покажу несколько возможных решений этих ошибок.
Совет № 1: Не используйте try / catch внутри блока Promise
Излишне использовать try/catch внутри блока Promise (кроме случая описанного в ошибке № 2). Если ваш код выдает ошибку внутри блока Promise, он будет автоматически обработан областью catch.
// Это избыточная конструкция new Promise((resolve, reject) => { try { const data = customFunction(); resolve(); } catch (e) { reject(e); } }) .then(data => console.log(data)) .catch(error => console.log(error));
Вместо этого позвольте коду выдать ошибку и обработать ее в функции catch следующим образом:
new Promise((resolve, reject) => { const data = customFunction(); resolve() }) .then(data => console.log(data)) .catch(error => console.log(error));
Это всегда будет работать за исключением следующего случая.
Совет № 2: Использование асинхронной функции внутри Promise
Теперь, скажем, в вашем блоке Promise вы хотите выполнить некоторую асинхронную задачу и добавить ключевое слово async
, и ваш код может выдать ошибку. Но теперь вы не можете обработать эту ошибку, даже если есть блок .catch().
// этот код не может обработать эту ошибку new Promise(async () => { throw new Error('Error message'); }).catch(e => console.log(e.message)); // Этот код также не сможет обработать ошибку (async () => { try { await new Promise(async () => { throw new Error('Error message'); }); } catch (e) { console.log(e.message); } })();
Всякий раз, когда я вижу асинхронные функции async внутри блока Promise, я всегда стараюсь отделить асинхронную логику от Promise, чтобы сделать этот блок синхронным. И это очень часто бывает достаточно. Но, тем не менее, в некоторых случаях вам может понадобиться асинхронная функция. В этом случае у вас нет другого выбора, кроме как обработать его вручную блоком try/catch.
new Promise(async (resolve, reject) => { try { throw new Error('Error message'); } catch (error) { reject(error); } }).catch(e => console.log(e.message)); // Или с использованием async/await (async () => { try { await new Promise(async (resolve, reject) => { try { throw new Error('Error message'); } catch (error) { reject(error); } }); } catch (e) { console.log(e.message); } })();
Совет № 3: Не забывайте про блок .catch()
Как правило, это ошибка, о которой вы, не подозреваете, пока не начнете тестирование. Или, если вы тот атеист, который не верит в написание тестов, то ваш код обязательно будет часто ломаться в производственной среде. Так как именно там всегда работает закон Мерфи.
«Все, что может пойти не так, пойдет не так», — закон Мерфи
Совет № 4: Не забывайте о Promise.all() и Promise.race()
Promise.all — все или ничего. Если у вас есть несколько Promise, которые не зависят друг от друга, вы можете выполнить их все одновременно. Promise.all выполняет один раз все Promise в массиве или отклоняет их всех, как только одно из них отклоняется. Использование Promise.all может значительно сократить общее время обработки массива Promise.
const { promisify } = require('util'); const sleep = promisify(setTimeout); async p1() => { await sleep(1000); } async p2() => { await sleep(2000); } async p3() => { await sleep(3000); } // Запуск кода последовательно (async () => { console.time('Начало выполнения'); await p1(); await p2(); await p3(); console.timeEnd('Завершение выполнения'); // Приблизительно 6 секунд })();
Выполнение этого кода занимает около 6 секунд. Теперь, если мы заменим код на Promise.all(), это займет около 3 секунд.
(async () => { console.time('Начало выполнения'); await Promise.all([p1(), p2(), p3()]); console.timeEnd('Завершение выполнения'); // Приблизительно 3 секунд })();
Метод race очень похож на all, но он возвращает выполнение первого завершенного из переданных Promise. Т.е. возвратит resolve или reject, в зависимости от того, что случится первым.
let p1 = new Promise((resolve, reject) => { setTimeout((data) => { resolve("p1") }, 3000); }); let p2 = new Promise((resolve, reject) => { setTimeout((data) => { resolve("p2") }, 5000); }); Promise.race([p1, p2]).then((value) => { console.log("Выполнение завершено ", value); }); // Выполнение завершено p1
Совет № 5: Не злоупотребляйте Promises
Promises делают ваш код медленнее; не злоупотребляйте ими. Обычно разработчикам советуют, использовать цепочку .then(), как можно чаще, чтобы их код выглядел элегантно. И вы не заметите никакой разницы, пока эта цепочка не станет слишком длинной. Если вы хотите увидеть это, запустите ваш код с помощью этой команды и откройте: chrome://tracing/ (работает только для Chrome)
node --trace-events-enabled fileName.js
Пример кода с ненужными блоками .then()
new Promise((resolve) => { // Некоторый код, который возвращает userData const user = { name: 'John Doe', age: 50, }; resolve(user); }).then(userObj => { const { age } = userObj; return age; }).then(age => { if (age > 25) { return true; } throw new Error('Возраст меньше чем 25'); }).then(() => { console.log('Возраст больше чем 25'); }).catch(e => { console.log(e.message); });
Если вы откроете файл журнала в инструменте трассировки Chrome, вы увидите несколько зеленых блоков, помеченных как Promise. И если вы щелкнете по любому из них, вы увидите, что все они выполняются за несколько миллисекунд. Следовательно, чем больше блоков обещаний, тем больше времени потребуется для их выполнения.
Пример кода с одиночным .then(), содержащий все синхронные операторы
new Promise((resolve, reject) => { // Некоторый код, который возвращает userData const user = { name: 'John Doe', age: 50, }; if (user.age > 25) { resolve(); } else { reject('Возраст меньше чем 25'); } }).then(() => { console.log('Возраст больше чем 25'); }).catch(e => { console.log(e.message); });
В этом случае будет меньше зеленых блоков Promise. И если вы увеличите цепочку .then(), вы заметите огромную разницу во времени, затрачиваемую обоими кодами.
Решение состоит в том, чтобы не использовать многократные Promise с синхронным кодом. Синхронный код должен находиться в одном блоке. Используйте это только несколько раз, если вы хотите выполнить некоторый асинхронный код.
https://habr.com/ru/post/484466/