Promise - специальный объект, который хранит своё состояние и используется для обработки результатов асинхронных операций.

Что будет в статье:

  • Основные задачи на Promsie, которые часто спрашивают на собеседованиях

  • Код задач с подробными комментариями

Чего в статье НЕ будет:

  • Теории про Promsie и Event Loop

  • Задач на Event Loop, показывающих приоритетность выполнения Promise, setTimeout и т.д.

Статья предназначена для тех, кто уже изучил теорию по Promise и хочет попрактиковаться в решении тестовых задач.

Основные методы Promise

Приведу список методов, которые будут использоваться а задачах:

  • "Promise.resolve(value)" - создаёт успешно выполнившийся Promise с результатом value

  • "Promise.reject(value)" - создаёт выполнившийся Promise с ошибкой

  • ".then" - обработчик событий Promise "onFulfilled" и "onRejected"

  • ".catch" - обработчик события Promise "onRejected"

  • ".finally" - дает возможность запустить один раз определенный участок кода, который должен выполниться вне зависимости от того, с каким результатом выполнился Promise

Задачи на .then

1. Случаи, когда .then возвращает или не возвращает значение

Promise.resolve("1")
    // Promise.resolve вернул "1"
    .then(data => {
        console.log(data); // => "1"
    })

    // .then ничего не вернул
    .then(data => {
        console.log(data); // => "undefined"
        return "2";
    })

    // .then вернул "2"
    .then(data => {
        console.log(data); // => "2"
    })

2. Второй аргумент .then

  • Первая задача

Promise.reject(1)
    // skip
    // обработать reject можно только в .catch
    .then(data => {
        console.log(data);
    })

    // второй аргумент .then вместо .catch
    .then(null, data => console.log(data)) // => 1

    // после обработки ошибки попадаем в .then
    // => "ok"
    .then(() => console.log('ok'));
  • Вторая задача

Promise.reject()
    // используем колбэки для .then и для .catch
    .then(
        data => console.log('ok'),   // => skip
        data => console.log('error') // => "error"
    )

3. null в .then

Promise.resolve("1")
    // skip
    .then(null)

    // придут данные из Promise.resolve
    .then(data => console.log(data)) // => "1"

Задачи на .catch

1. Ошибка из Promise.reject

Promise.reject("Api Error")
    // skip из-за Promise.reject
    .then(data => console.log('ok'))

    // обработка ошибки
    .catch(error => {
        console.log(error); // => "Api Error"
        return "1";
    })

    // catch вернул "1"
    .then(data => {
        console.log(data); // => "1"
    })

2. Ошибка из Promise.reject в .catch

Promise.reject()
    // обработка Promise.reject
    .catch(() => {
        console.log('error1'); // => "error1"
        return Promise.reject();
        // аналогично
        // return reject();
    })

    // обработка Promise.reject из предыдущего .catch
    .catch(() => {
        console.log('error2'); // => "error2"
    })

3. Ошибка throw new Error

Promise.resolve()
    .then(data => {
        // возникновение ошибки
        throw new Error('Api Error');
        // не имеет значения, что вернули
        return 1;
    })

    // skip, потому что предыдущий .then бросил ошибку
    .then(data => console.log('ok'))

    // обработка ошибки
    .catch(error => {
        console.log(error.message); // => "Api Error"
        return "2";
    })

    // .catch вернул "2"
    .then(data => {
        console.log(data); // => "2"
    })

4. Необработанная ошибка в Promise

  • Необработанная ошибка в Promise не влияет на работу программы, т.к. Promise не выпускает ошибку за свои пределы.

Promise.resolve()
    .then(() => {
        throw new Error('Api Error');
    })

// код будет работать
setTimeout(() => {
    console.log('setTimeout'); // => "setTimeout"
}, 1000);

5. null в .catch

Promise.reject("Api Error")
    // skip: ошибку не обработали
    .catch(null)

    // skip из-за необработанной ошибки
    .then(data => console.log('ok'))

    // обработка ошибки
    .catch(error => console.log(error)) // => "Api Error"

    // .then выполнится
    .then(data => console.log('ok')) // => "ok"

Задачи на .finally

  • Первая задача

Promise.resolve()
    .then(() => {
        return "1";
    })

    // .then вернул "1", но .finally пропустит его мимо себя
    .finally(data => { 
        console.log(data); // => "undefined"
        return "2";
    })

    // из .finally вернули "2", но результат берется из предыдущего .then или .catch
    .then(data => console.log(data)) // => "1"
  • Вторая задача

Promise.reject()
    // .finally выполняется в любом случае: даже при возникновении ошибки
    .finally(data => { 
        console.log('finally'); // => "finally"
    })

Задачи на несколько Promise

  • Если несколько Promise, то ".then" будет выполняться последовательно для каждого:

Promise.resolve()
    .then(() => console.log(1))  // "Первый"
    .then(() => console.log(2))  // "Третий"

Promise.resolve()
    .then(() => console.log(11)) // "Второй"
    .then(() => console.log(12)) // "Четвертый"
  • При ".catch" аналогично:

Promise.resolve()
    .then(() => console.log(1))                          // "Первый"
    .then(() => { console.log(2); throw new Error(); })  // "Третий"
    .catch(() => console.log(3))                         // "Пятый"
    .then(() => console.log(4))                          // "Седьмой"

Promise.resolve()
    .then(() => console.log(11))                         // "Второй"
    .then(() => { console.log(12); throw new Error(); }) // "Четвертый"
    .catch(() => console.log(13))                        // "Шестой"
    .then(() => console.log(14))                         // "Восьмой"

Задача на бесконечные микротаски

В качестве бонуса покажу, как повесить браузер микротасками, которыми являются Promise:

const foo = () =>  {
    Promise.resolve().then(() => foo())
}

foo();

Заключение

В статье я собрал базовые задачи с собеседований. Чтобы понять Promise, необходимо изучить теорию и приступать к практике. Стоит практиковать задачи в различных вариациях: расширять задачи, изменять существующие примеры и отслеживать изменения.

На этом всё. И да прибудет с нами сила!

Комментарии (17)


  1. anatoly314
    12.10.2021 09:29
    -4

    Только лучше все таки Promise не пользоваться по возможности. Где встречаю, стараюсь переписать на async/await.


    1. anton-sergeenkov Автор
      12.10.2021 10:47

      На собеседованиях в основном по Промисам гоняют =)


    1. aamonster
      12.10.2021 11:10
      +6

      Ну, это и есть промисы, просто в более удобном и понятном виде)


    1. hardovsky
      12.10.2021 14:01

      А ничего что async/await просто синтаксический сахар над Promise?


      1. anatoly314
        12.10.2021 18:44
        -1

        Я прекрасно это знаю, и использование этого syntactical sugar позволяет избежать многих ошибок как например в примере выше.


        1. qrKot
          13.10.2021 19:13
          +2

          для того, чтобы пользоваться syntactic (не syntactical, тактического в нем ничего нет) sugar, очень полезно знать, поверх чего он работает... Тем более любая асинхронная функция таки возвращает Promise.


  1. Alexandroppolus
    12.10.2021 10:30
    +2

    с собеса, несложная:

    const p = Promise.reject();
    p.then(() => console.log('ok'));
    p.catch(() => console.log('error'));

    Объяснить, почему выскакивает "UnhandledPromiseRejection", хотя хотя вроде бы навесили catch.


    1. anton-sergeenkov Автор
      12.10.2021 10:46
      +1

      Вообще, у меня ошибка не возникла, но предположу, потому что здесь нет чейнинга

      1. Создали промис

      2. Обработали только then - тут нет catch и будет ошибка

      3. Обработали только .catch отдельно


        Если так переписать, то будет корректно

      Promise.reject()
          .then(() => console.log('ok'))
          .catch(() => console.log('error'))


      1. Suvitruf
        12.10.2021 12:38

        Так это же разные примеры. То, что вы написали — это не то же самое, что написал Alexandroppolus.
        Когда вы навешивает then'ы после создания промиса, то они не выстраивают одну цепочку, а будут выполняться параллельно.


    1. aamonster
      12.10.2021 11:18
      +1

      Красиво, пришлось думать. Не сразу допёр "сосчитать" промисы, попадающие в очередь (в смысле что p.then() – это новый промис).


      1. aamonster
        12.10.2021 16:30

        Upd: Забавно, похоже все, кроме меня, мыслят чейнами, а не отдельными промисами. Надо подумать, какое восприятие проще (видимо, чейны) и нет ли в мышлении чейнами каких-то подводных камней.


        1. Alexandroppolus
          12.10.2021 16:45
          +1

          Надо мыслить отдельными чейнами )

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

          вот, например, в таком раскладе:

          const p = Promise.reject();
          p.then(() => console.log('ok'));
          p.then(() => console.log('ok'));

          выскочит 2 "Unhandled", потому что 2 конечных промиса, хотя всего 3 отклоненных.


    1. Suvitruf
      12.10.2021 13:14

      Потому что все новые чейны на уже созданный промис навешиваются, а не в одну цепочку.
      У вас:

      p.then()
      p.catch()

      Не связаны друг с другом и выполняются параллельно.

      Если нужен один чейн, то либо в одну цепочку вешайте:
      const p = Promise.reject();
      p.then(() => console.log('ok'))
       .catch(() => console.log('error'));


      Либо сразу при создании, как anton-sergeenkov написал.


  1. korsetlr473
    12.10.2021 17:25

    • ".then" - обработчик событий Promise onRejected"

    • ".catch" - обработчик события Promise "onRejected"

    чего?


    1. anton-sergeenkov Автор
      12.10.2021 17:50

      у .then 2 аргумента: первый обрабатывает "onFulfilled", а второй "onRejected" - таким образом, .then можно использовать как замена .catch

      .catch, в свою очередь, обрабатывает только "onRejected"


      1. noodles
        15.10.2021 21:45

        Кстати, важный момент, если использовать формат .then(resolveHandler, rejectHandler), то rejectHandler не поймает ошибку, возникшую внутри resolveHandler в текщуем then-е.
        Или в следующем then-е её ловить, или проще и безопаснее всегда ловить всё в catch.


  1. Fodin
    13.10.2021 23:07
    +1

    Очень хотел такой материал и обрадовался, когда заголовок увидел. Но был обескуражен (ноль иронии), когда не нашел задач. Задача - это "что надо сделать" (условие) и "как это сделать" (ответ, решение). А тут, получается, примеры кода с пояснениями. В таком виде оно все тоже ценно, но обещаны-то задачи. Эх. Может, есть где-то именно задачник на промисы и async/await, который проведет по всем нюансам?