Распространенные ошибки при использование Promise в Javascript

Spread the love

Если вы постоянно работаете с 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 с синхронным кодом. Синхронный код должен находиться в одном блоке. Используйте это только несколько раз, если вы хотите выполнить некоторый асинхронный код.

Была ли вам полезна эта статья?
[25 / 3.1]

Spread the love
Подписаться
Уведомление о
guest
1 Комментарий
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Harry
Harry
4 лет назад