Від джуніора про Promise в JavaScript: на прикладі роботи піцерії

💡 Усі статті, обговорення, новини для початківців — в одному місці. Приєднуйтесь до Junior спільноти!

Всім вітання. Мене звуть Алла і я Backend developer в компанії Weblium. Минулого літа отримала можливість пройти публічну співбесіду у пана Сергія Бабіча. Це дійсно крутий досвід, який дав розуміння, де потрібно вдосконалити свої знання та навички. Для мене особисто кращим форматом засвоєння знань є написання статті, оскільки в процесі її підготовки я більш глибоко занурююсь в тему. З різних причин вихід статті затягнувся🤷‍♀️, але то деталі.

В цій публікації вирішила пропрацювати Promise в JavaScript. Це питання, на мою суб’єктивну думку, в інтерв’ю я розкрила слабко. Тому тут не буде якихось унікальних відкриттів для обізнаної аудиторії. Загальна мета — структурувати власні знання про Promise. Можливо, комусь зможу допомогти зрозуміти чи пригадати якісь аспекти або ваша критика поглибить мої знання. Але одне знаю напевно — ця стаття допомогла мені не на одному інтервʼю.

Запрошую познайомитись з обіцянкою в JavaScript разом на прикладі замовлення піци онлайн в піцерії. Обіцяю, буде цікаво 😏.

Однопотоковий JavaScript — причина появи обіцянок

Почнемо з того, що JavaScript — це однопотокова мова програмування. Це коли код виконується послідовно — кожен крок має свою чергу на виконання і відсутні паралельні процеси, як результат => одночасно може бути активною лише одна операція або функція. Всі операції виконуються по черзі або у запланований час.

Проведемо аналогію: уявімо собі піцерію, де працює лише один кухар. Він готує піцу від початку до кінця самостійно: розкатує тісто, додає начинку, випікає, нарізає та подає клієнтам. Але є проблема — цей кухар працює лише в одному потоці. Це означає, що він може робити тільки одну дію за раз: якщо він замішує тісто, то не може одночасно випікати піцу чи приймати нові замовлення.

Так само працює JavaScript. Всі операції виконуються послідовно, і поки одна триває — інші змушені чекати.

Це може стати проблемою, коли потрібно:

  • готувати кілька піц одночасно (обробка кількох серверних запитів);
  • нарізати інгредієнти паралельно з випіканням (реакція на дії користувачів без блокування основних процесів);
  • приймати нові замовлення, поки старі ще в роботі (обробка великих обсягів даних);
  • своєчасно подавати страви, щоб клієнти не чекали вічність (завантаження та відображення ресурсів).

Без асинхронного підходу кухар змушений чекати, поки тісто підніметься, перш ніж перейти до наступного кроку, а клієнти залишаються голодними. Бо якщо ваш потік зайнятий зворотнім відліком, то він не може виконувати нічого іншого, поки не завершиться цей відлік. Саме тому потрібно розумне управління процесами — так з’являються асинхронні операції.

Mи не можемо просто наказати JavaScript зупинитися та зачекати перед виконанням наступного рядка коду, оскільки це заблокує потік. Нам знадобиться якийсь спосіб поділу роботи на асинхронні фрагменти. Уникнути перевантаження, тривалого очікування та блокування операцій можна кількома шляхами. Щоб розв’язати цю проблему, в програмуванні є три основні інструменти:

  • Callback-функції — якби кухар кликав помічника кожного разу, коли потрібно щось зробити.
  • Promises — якби кухар міг доручити частину роботи й отримати результат пізніше.
  • Async/Await — спосіб працювати з обіцянками так, щоб усе виглядало послідовним.

Почала з цього, бо без розуміння того, як працює JavaScript, неможливо зрозуміти його інструменти. І саме Promise стали тим рішенням, яке дозволяє нашому кухарю ефективніше управляти процесами. Тож давайте розбиратися, як вони працюють!

Що ж це за звір такий — Promise?

Для початку цей звір буквально означає — обіцянка, що може бути виконана чи відхилена, а в контексті JavaScript є інструментом асинхронного програмування. При пошуку інформації користувалася величезною кількістю джерел, де визначення інструменту трохи різняться або немає чіткого формулювання. Тож я виділила основне, що є спільним:

Promise — це об’єкт, який є результатом асинхронної операції (яка ще незавершена, але буде завершена в майбутньому) та містить відповідь про успішне виконання операції або про помилку в процесі її виконання. Проте замість повернути одразу кінцеве значення метод повертає promise — зобов’язання надати це значення в якийсь момент у майбутньому.

Повернемось до аналогії: уявімо, що в нашій піцерії з’явився новий сервіс доставки. Клієнт телефонує та замовляє піцу, а кухар замість того, щоб чекати, поки вона спечеться, просто дає обіцянку: «Ваша піца буде готова через 30 хвилин!». Тепер клієнт може спокійно займатися своїми справами, знаючи, що йому повідомлять, коли замовлення буде готове.

Так само працює Promise у JavaScript. Він дозволяє виконувати асинхронні операції, не блокуючи основний потік виконання коду. Обіцянки потрібні для керованішого та чистішого коду під час виконання асинхронних завдань, таких як:

  • Виклики до API (наприклад, запит на сервер щодо списку замовлень піцерії).
  • Взаємодія з користувачем (натискання кнопки «замовити» не повинно блокувати всю програму).
  • Анімації (наприклад, плавне з’явлення повідомлення про статус замовлення).

Конструктор Promise

У JavaScript Promise створюється за допомогою конструктора Promise, який приймає функцію-виконавець (executor). Вона запускається автоматично і отримує два параметри:

  • resolve — викликається при успішному виконанні операції.
  • reject — викликається у разі помилки.

Синтаксис промісу:

const pizzaOrder = new Promise((resolve, reject) => {
    setTimeout(() => {
      let success = Math.random() > 0.2; // 80% шанс, що піца буде готова
      if (success) {
        resolve("Ваша піца готова!");
      } else {
        reject("Вибачте, ми не встигли приготувати піцу.");
      }
    }, 3000);
  }); 

Властивості обіцянок

Об’єкт, який повертається конструктором new Promise, має свої внутрішні властивості — результат (result) та стани (state). Важливою особливістю обіцянки є стан. Всього їх три і в одному з яких завжди обов’язково перебуває об’єкт:

  • «pending» (очікування) — стартовий стан, незалежно від результату виконання асинхронної операції;
  • «fulfilled» (виконано) — коли в результаті виконання операції викликається метод resolve;
  • «rejected» (відхилено) — коли в результаті асинхронної операції викликається метод reject.

Важливо також враховувати властивість result, залежно від стану змінюється і її значення:

NumberStateResult
1«pending»undefined
2«fulfilled»value (data)
3«rejected»error

Приклад виконання успішного замовлення:

pizzaOrder.then((message) => {
    console.log(message); // "Ваша піца готова!"
});

Приклад обробки помилки:

pizzaOrder.catch((error) => {
    console.log(error); // "Вибачте, ми не встигли приготувати піцу."
});

Promise виконує свою задачу, що в більшості випадків потребує часу для завершення всіх операцій. Після чого викликається один із методів resolve або reject, що залежить від результату роботи функції-виконавця. Ці методи змінюють стани, які надає конструктор new Promise. Тому знання властивостей є необхідним для розуміння роботи обіцянки. Ще важливо те, що Promise вважається виконаним в стані fulfilled або rejected, але не в стані очікування.

Обробники — Promise Handlers

Функції, що дозволяють керувати результатами виконання промісів (тобто керувати виконанням асинхронних операцій) — це Promise handlers або обробники.

Вони використовуються для виконання певних дій залежно від того, завершилася операція успішно чи виникла помилка. Методами обробки обіцянок є:

  • .then() — викликається у разі успішного виконання промісу. Це буває у випадку, коли проміс перейшов у стан «виконано» (fulfilled). Крім того, .then() повертає новий проміс, завдяки чому можна створювати ланцюжки з кількох .then();
  • .catch() — буквально обробник та відловлювач помилок. Викликається у разі, якщо проміс був відхилений (rejected). Його основним завданням є обробка помилок, що виникли під час виконання асинхронної операції;
  • .finally() — викликається незалежно від результату виконання промісу і є його завершенням. Обробник .finally() не приймає аргументів. Призначений для виконання дій, які мають відбутися в будь-якому випадку (наприклад для очищення ресурсів). Також обробник .finally() не повинен нічого повертати. В тих випадках, коли він щось повертає — це значення ігнорується.
pizzaOrder
    .then((message) => {
        console.log(message); // "Ваша піца готова!"
    })
    .catch((error) => {
        console.log(error); // "Вибачте, ми не встигли приготувати піцу."
    })
    .finally(() => {
        console.log("Дякуємо за ваше замовлення!");
    });
Виключення: коли обробник .finally() видає помилку. В такому випадку помилка переходить до наступного обробника замість будь-якого попереднього результату проміса.

Обробники ще називають прикутими промісами, оскільки вони використовуються для пов’язування подальших дій з обіцянкою, яка виконується. Вони дозволяють ефективно управляти асинхронними операціями та забезпечувати чистий код.

Переваги Promise над callback

Раніше для асинхронного програмування використовували callback-функції, але вони створювали проблему callback hell — вкладеність у вкладеності, яка ускладнює читання та підтримку коду.

pizzaOrder((order) => {
    prepareDough(order, (dough) => {
        addStuffing(dough, (піца) => {
            bake(піца, (readyPizza) => {
                submit(readyPizza);
            });
        });
    });
});

У цьому прикладі кілька асинхронних операцій вкладені одна в одну, що ускладнює розуміння та підтримку коду.

callback and Promise

Promise допомагає вирішити цю проблему, дозволяючи обробляти асинхронні операції в більш чистому і зрозумілому вигляді:

pizzaOrder()
    .then(prerareDough)
    .then(addStuffing)
    .then(bake)
    .then(serve)
    .catch(handleError);

Проміс дозволяє керувати асинхронними операціями так само, як у нашій піцерії система доставки — клієнт отримує обіцянку, що його піца буде готова, і може займатися своїми справами. Це робить код чистішим, керованішим і зрозумілішим!

На відміну від колбеків Promise вже має вбудовані зворотні виклики resolve і reject. Всі дії відбуваються «під капотом» і завдяки цьому ваш код стає чистішим, та і не потрібно писати все руками.

Ланцюжки Promise

Уявіть, що ви власник піцерії, і кожне замовлення проходить кілька етапів: прийом замовлення, приготування, випікання, пакування та доставка. Кожен етап виконується асинхронно, і наступний може початися лише після завершення попереднього. Це ідеальна аналогія для розуміння ланцюжків промісів.

Promise Chaining — це техніка в JavaScript, яка дозволяє виконувати послідовність асинхронних операцій одну за одною. Це досягається шляхом використання методів .then(), які дозволяють обробляти результати кожної асинхронної операції та передавати дані до наступної операції. Розглянемо процес приготування піци на прикладі:

// Перший проміс – замішування тіста
const makeDough = () => { // Оголошуємо makeDough як функцію
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Тісто готове");
    }, 1000);
  });
};

// Другий проміс – додавання інгредієнтів
const addIngredients = (data) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(data + " -> Інгредієнти додані");
    }, 1000);
  });
};

// Третій проміс – випікання піци
const bakePizza = (data) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(data + " -> Піца готова!");
    }, 2000);
  });
};

// Ланцюжок промісів
makeDough() // Викликаємо функцію makeDough()
  .then((result) => {
    console.log(result);
    return addIngredients(result);
  })
  .then((result) => {
    console.log(result);
    return bakePizza(result);
  })
  .then((result) => {
    console.log(result);
    console.log("Смачного!");
  })
  .catch((error) => {
    console.error("Помилка приготування піци:", error);
  })
  .finally(() => {
    console.log("Процес завершено.");
  });

Основні принципи ланцюжків промісів:

  • послідовність виконання — кожен проміс у ланцюжку виконується після успішного виконання попереднього;
  • передача даних — результат виконання попереднього промісу передається до наступного через метод .then();
  • обробка помилок — помилки, що виникають в будь-якому промісі, можуть бути оброблені за допомогою методу .catch();
  • завершення ланцюжка — метод .finally() виконується незалежно від того, чи був проміс виконаний успішно, чи ні.

Де застосовуються ланцюжки промісів

Використання ланцюжків дозволяє розширити можливості для застосування обіцянок для вирішення більшого спектра задач. Вони корисні у випадках, коли необхідно виконувати послідовність асинхронних операцій. Найбільш поширеними задачами є:

  • Робота з API (отримання інформації про клієнта і його замовлення).

    fetch('https://api.example.com/user')
         .then(response => response.json())
         .then(user => {
            console.log('Користувач:', user);
            return fetch(`https://api.example.com/posts?userId=${user.id}`);
         })
         .then(response => response.json())
          .then(posts => {
            console.log('Пости користувача:', posts);
         })
         .catch(error => {
            console.error('Помилка:', error);
         });
  • Обробка послідовних дій з базою даних (оновлення статусу замовлення).

    db.insert(data)
        .then(result => db.update(result.id, newData))
        .then(updatedResult => db.delete(updatedResult.id))
        .catch(error => console.error('DB operation failed', error));
  • Завантаження ресурсів (отримання фото меню).

    function loadImage(url) {
        return new Promise((resolve, reject) => {
        const img = new Image();
        img.src = url;
        img.onload = () => resolve(`Зображення ${url} завантажено`);
        img.onerror = () => reject(`Помилка завантаження зображення ${url}`);
      });
    }
    
    // Ланцюжок промісів для послідовного завантаження зображень
    loadImage('https://example.com/image1.jpg')
        .then((message) => {
           console.log(message);
           return loadImage('https://example.com/image2.jpg');
        })
        .then((message) => {
          console.log(message);
          return loadImage('https://example.com/image3.jpg');
        })
       .then((message) => {
          console.log(message);
          console.log('Усі зображення успішно завантажені');
        })
        .catch((error) => {
           console.error(error);
        });
  • Серіалізація асинхронних операцій (етапи доставки піци).

    const step1 = () => {
       return new Promise((resolve, reject) => {
           setTimeout(() => {
             resolve('Крок 1 виконано');
          }, 1000);
       });
    };
    
    const step2 = (data) => {
       return new Promise((resolve, reject) => {
           setTimeout(() => {
              resolve(`${data} -> Крок 2 виконано`);
           }, 1000);
       });
    };
    
    const step3 = (data) => {
       return new Promise((resolve, reject) => {
           setTimeout(() => {
              resolve(`${data} -> Крок 3 виконано`);
           }, 1000);
       });
    };
    
    step1()
        .then(result => {
           console.log(result);
           return step2(result);
        })
        .then(result => {
           console.log(result);
           return step3(result);
        })
        .then(result => {
           console.log(result);
        })
        .catch(error => {
           console.error('Помилка:', error);
        });

Загалом, ланцюжки промісів дозволяють зробити код більш читабельним, структурованим та зручним у використанні. Вони особливо корисні в ситуаціях, коли необхідно виконати серію взаємозалежних асинхронних дій, наприклад, у нашій піцерії!

Статичні методи обіцянок з прикладами

Крім властивостей, станів та обробників Promises передбачають також вбудовані статичні методи — Static methods. Це методи, які надаються безпосередньо об’єкту Promise і дозволяють працювати з ним на більш високому рівні. Ключовим призначенням є: вирішення типових завдань, пов’язаних з обробкою кількох промісів або створенням нових промісів з певними властивостями.

Раніше ми вже згадували Promise.resolve(value) та Promise.reject(error) в контексті конструктора. Їх використання є найбільш поширеним, але розглянемо все попорядку:

  • Promise.resolve() — піца вже готова

    Уявімо, що одна з піц вже була приготована заздалегідь і просто очікує на видачу. Метод Promise.resolve() створює проміс, який виконується негайно з переданим значенням. Це зручно, коли потрібно обгорнути значення у проміс або імітувати вже готовий результат.

    const readyPizza = Promise.resolve("Піцца Маргарита готова");
    readyPizza.then((result) => console.log(result));
    // Виведе: Піцца Маргарита готова
  • Promise.reject() — піцу зіпсували

    Трапляється й таке, що піцу зіпсували ще до того, як почали готувати — наприклад, закінчилися інгредієнти. Promise.reject() створює проміс, який відхиляється негайно з вказаною причиною. Зручно для тестування помилок або миттєвого повернення відмови.

    const spoiledPizza = Promise.reject("Інгредієнти закінчилися");
    spoiledPizza.catch((error) => console.error(error));
    // Виведе: Інгредієнти закінчилися
  • Promise.all() — всі замовлення виконані

    Уявімо, що у нашій піцерії клієнт замовив кілька піц, і кухарі готують їх одночасно. Ми хочемо видати замовлення тільки тоді, коли всі піци готові. Саме так працює Promise.all(). Цей метод приймає масив обіцянок і повертає новий проміс, який виконується, коли всі проміси завершені. Якщо хоча б один проміс відхилено, весь ланцюжок теж буде відхилений.

    const promise1 = Promise.resolve("Зображення 1 завантажено");
    const promise2 = Promise.resolve("Зображення 2 завантажено");
    Promise.all([promise1, promise2])
        .then((results) => console.log(results)) // Виведе: ['Зображення 1 завантажено', 'Зображення 2 завантажено']
        .catch((error) => console.error(error));
  • Promise.any() — хто перший принесе піцу

    Уявімо ситуацію: у піцерії працює кілька кур’єрів, і наше завдання — отримати першу доставлену піцу, незалежно від того, хто її приніс. Promise.any() виконується при першому успішному промісі. Якщо всі проміси відхилені, буде повернена помилка AggregateError.

    const promise1 = Promise.reject("Помилка 1");
    const promise2 = Promise.resolve("Успіх");
    const promise3 = Promise.reject("Помилка 2");
    Promise.any([promise1, promise2, promise3])
        .then((result) => console.log(result)) // Виведе: Успіх
        .catch((error) => console.error(error));
  • Promise.allSettled() — всі кур’єри відзвітували

    Уявімо, що ми відправили кілька кур’єрів, і хочемо дізнатися, хто з них виконав доставку, а хто — ні. Promise.allSettled() повертає масив об’єктів, які містять статус кожного промісу.

    const promise1 = Promise.resolve("Успіх");
    const promise2 = Promise.reject("Помилка");
    Promise.allSettled([promise1, promise2])
        .then((results) => console.log(results));
        // Виведе: [{status: 'fulfilled', value: 'Успіх'}, {status: 'rejected', reason: 'Помилка'}]
  • Promise.race() — хто швидше приготує піцу

    Цей метод схожий на змагання серед кухарів: перша піца, що буде готова, негайно відправляється клієнту. Promise.race() повертає результат першого виконаного промісу, незалежно від його статусу.

    const promise1 = new Promise((resolve) => setTimeout(() => resolve("Швидший"), 1000));
    const promise2 = new Promise((resolve) => setTimeout(() => resolve("Повільніший"), 2000));
    Promise.race([promise1, promise2])
        .then((result) => console.log(result)) // Виведе: Швидший
        .catch((error) => console.error(error));
  • Promise.try() — спроба приготувати новий рецепт

    Уявімо, що шеф-кухар вирішив експериментувати з новим рецептом піци, але не знає, чи вона вийде смачною. Promise.try() (нестандартний метод, але часто використовується в бібліотеках) дозволяє обробляти винятки в асинхронних функціях без додаткових try/catch блоків. Я знайшла обмежені дані на порталі mdn web docs. Однак ще зустрічала інформацію, що він користується популярністю у деяких бібліотеках, таких як Bluebird. Основна мета цього методу — спростити роботу з функціями, які можуть кидати винятки, і автоматично обробляти їх як відхилені проміси.

    const fetchData = () => {
        if (Math.random() > 0.5) {
           return "Успіх!";
        } else {
           throw new Error("Помилка під час виконання!");
        }
    };
    Promise.try(() => fetchData())
        .then((result) => console.log(result))
        .catch((error) => console.error(error.message));

Статичні методи промісів, такі як .all(), .any(), .allSettled(), .race(), .try(), корисні для вирішення конкретних завдань. Хоча вони використовуються рідше, у правильних ситуаціях вони можуть значно спростити асинхронну логіку. У піцерії, як і в програмуванні, важливо вибрати правильний підхід для ефективної роботи! 🍕

Скасування Promise: старі та сучасні підходи

Уявімо, що ви керуєте піцерією, і клієнти постійно роблять замовлення. Але інколи буває так, що хтось раптово передумав або вирішив змінити своє замовлення. Що робити в такому випадку? Саме для таких ситуацій у програмуванні існує механізм Promise Cancellation — спосіб скасовувати асинхронні операції, якщо вони більше не потрібні.

Promise Cancellation є важливим аспектом управління асинхронними операціями. Завдяки йому можливо економити ресурси та підвищувати ефективність програми.

За замовчуванням в JavaScript обіцянки не підтримують скасування. Якщо проміс був запущений (наприклад, приготування піци) вже розпочався, то його не можна скасувати ззовні.

Але уявімо, що клієнт телефонує та каже: «Я передумав, не потрібно готувати мою піцу!» — у такому випадку варто мати можливість скасувати замовлення, щоб не витрачати інгредієнти та ресурси кухні даремно. Є декілька варіантів, які дозволяють виконувати операцію скасування. Більшість джерел рекомендує використовувати вже готові інструменти в різних бібліотеках (наприклад bluebird або p-cancelable).

Один із варіантів реалізації — використання AbortController, який дозволяє зупиняти запити fetch, що дуже схоже на ситуацію скасування замовлення в піцерії:

const fetchData = (url) => {
     const controller = new AbortController();
     const signal = controller.signal;
            
     const promise = new Promise((resolve, reject) => {
          fetch(url, { signal })
              .then(response => response.json())
              .then(data => resolve(data))
              .catch(error => reject(error));
           });
           
      promise.cancel = () => {
           controller.abort();
      };
      return promise;
};

const dataPromise = fetchData('https://api.example.com/data');
     
// Скасування промісу через 1 секунду
setTimeout(() => {
     dataPromise.cancel();
}, 1000);
            
dataPromise
    .then(data => {
        console.log('Дані отримано:', data);
    })
    .catch(error => {
        console.error('Помилка:', error);
    });

Сучасні підходи: Promise.withResolvers()

Новим та більш зручним способом скасування обіцянок є Promise.withResolvers(), доданий у 2024 році. Він дозволяє більш явно керувати промісами та їх станами.

Уявімо, що шеф-кухар отримав замовлення, але офіціант раптово каже: «Скасуй піцу для цього столика!». Саме тут у гру вступає Promise.withResolvers(): за відгуками розробників (оцінка на основі прочитаних публікацій, наприклад стаття «Mastering promise cancellation in JavaScript» або «Новий метод Promise.withResolvers тепер доступний у браузерах») — це зручний та корисний інструмент.

const { promise, resolve, reject } = Promise.withResolvers();

const cookPizza = (назва) => {
    console.log(`Готуємо піцу: ${назва}...`);
    setTimeout(() => resolve(`${назва} готова!`), 3000);
};

cookPizza("Маргарита");

// Скасовуємо замовлення через 1 секунду
setTimeout(() => {
    console.log("Замовлення скасовано, піца не потрібна!");
    reject("Скасування замовлення.");
}, 1000);

promise
    .then((result) => console.log(result))
    .catch((error) => console.error("Помилка: ", error));

Promise.withResolvers() є статичним методом, який повертає об’єкт, що містить новий Promise об’єкт і дві функції для його вирішення (resolve) або відхилення (reject).

Альтернативний підхід: Observables

Інший спосіб керування потоками запитів — це Observables, які працюють як стрічка подій. Уявіть, що кур’єр отримує одразу кілька замовлень і може приймати або скасовувати їх на льоту. Це чудово підходить для сценаріїв, коли треба реагувати на безперервний потік даних, наприклад, оновлення статусу доставки піци в реальному часі.

Скасування асинхронних операцій — важливий інструмент оптимізації ресурсів та покращення користувацького досвіду. У піцерії, як і в коді, завжди корисно мати можливість швидко зупинити непотрібні процеси, щоб уникнути марнування ресурсів! 🍕😃

Висновок

Як і в справжній піццерії, де важливо керувати замовленнями, оптимізувати процеси та швидко реагувати на зміни, Promise допомагають нам ефективно працювати з асинхронними операціями. Ми навчилися:

  • правильно обробляти кілька одночасних запитів;
  • чекати на завершення всіх процесів;
  • скасовувати непотрібні запити;
  • чекати на завершення всіх процесів, а також скасовувати непотрібні запити, коли клієнт передумав або з’явився більш терміновий пріоритет.

У світі розробки, як і в бізнесі, важливо не лише створювати, але й вміти зупиняти зайві процеси, щоб зберегти ресурси та підвищити ефективність. І тепер ти знаєш, як це зробити! Тож нехай твій код буде таким же гарячим і смачним, як щойно приготована піца! 🍕🔥

👍ПодобаєтьсяСподобалось24
До обраногоВ обраному11
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Заходячи в цей тред вже розумів, що авторку зараз розкатають у коментарях

Аффторка, а хто ви за освітою, якще це не є таємницею?

А слабо все асинки и авейты на сигналы переписать?

Дякую за ваш цінний коментар!

Ви маєте наувазі якісь конкретні

асинки и авейты

чи взагалі всі?
Нагадую: стаття буквально " ...про Promise в JavaScript ..."

звісно, лише ті що були наведені у вашому коді npm install @preact/signals-react Хоча назва пакету містить @preact, вона розроблена для React і використовує ті самі основні концепції, що й бібліотека Signals від Preact.

Якщо ви уважно перечитаєте статтю та запропоновані мною приклади коду, можливо, ви таки побачите, що там представлені приклади Promise та все з ними повʼязане. Це стаття про конкретний інструмент в JavaScript.

Промисы — это всего лишь базовый механизм для работы с асинхронностью, но они не умеют отменять операции. Использование AbortController/AbortSignal и сигналов — это современный и рекомендуемый способ добавить контроль над асинхронными запросами, что особенно важно в реальных приложениях с динамичным интерфейсом и множеством параллельных запросов.Использование сигналов (AbortController/AbortSignal) — это следующий шаг для улучшения контроля и управления промисами в реальных приложениях, что способствует
предотвращению гонок состояний — когда несколько асинхронных операций выполняются одновременно, сигналы позволяют отменить устаревшие запросы, чтобы только самый актуальный запрос мог обновить состояние приложения. Это муст хаве на сегодняшний день! Вы этого к сожалению не понимаете, т.к. у вас минимальное инженерное мышление...Использование сигналов (AbortController) с промисами — это следующий шаг в развитии практик работы с асинхронностью, необходимый для создания надёжных, производительных и удобных веб-приложений в 2025 году. Это не просто рекомендация, а современный стандарт, который помогает избежать распространённых ошибок и улучшить качество кода. Пишите код современно!

не знаю как у вас, но у нас на 122 за инженерный подход!
инженерный подход начинается с математики, в том числе дискретной математики и теории множеств

аборт контроллер не является частью спецификации промисов
аборт контроллер является частью спецификации
спека фетч апи имеет частичное пересечение со спекой промисов потому что тоже использует промисы
миллион других апи тоже используют промисы при этом не поддерживают аборт контроллер — вы бы знали это, если бы вам хоть изредка доводилось открывать хоть какую-то документацию

сигналы не являясь частью спеки промисов не входят множество того, что статья про промисы должна покрывать

как у нас говорят на 122, инженер без понимания математики — это не инженер!
так что учите математику, учите документацию, а только-только потом уже учите других, и уж тем более, боже упаси, говорите кому-то что-то про инженерное мышление — на сегодня вы ни малейшего понятия не имеете что это такое!

Полностью согласен, что знание основ, включая дискретную математику и теорию множеств, является фундаментом для настоящего инженера-программиста, которое дает обучение в технических вузах, где а также знакомят с инженерными подходами к решению программерских задач.
Что касается темы AbortController и промисов, вы абсолютно правы: AbortController — это часть спецификации Fetch API и не входит напрямую в спецификацию промисов. Промисы — это универсальный механизм для работы с асинхронностью, который используется во многих API, но не все из них поддерживают отмену через AbortController.
Главная моя цель была подчеркнуть важность именно управления жизненным циклом асинхронных операций в современных реакт приложениях, где fetch и AbortController, AbortSignal — один из самых распространенных и рекомендованных инструментов для отмены запросов. Это не попытка объединить AbortController и промисы в одну спецификацию, а показать, что при работе с fetch (который возвращает промис) контроль через AbortController — это современная инженерная практика при разработке ПО. Если вам, что то не понятно, пишите объясню как это было по монадам... Советую вам повторить теорию категорий, если речь зашла о математике, а именно прочитать про монады, как абстракции, которые помогают строить цепочки вычислений с управлением побочными эффектами и при этом строго следуя определённым математическим законам, а то у нас возникают сомнения о вашей принадлежности к 122-ой!

боюсь что-то кому-то объяснять вы сможете ой как не скоро...

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

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

раз уж вы решили «объяснять» монады, пожалуйста, на будущее имейте в виду, что:
Промис — это монадоподобная структура, но не полная монада, потому что нарушает ассоциативность по времени выполнения и побочным эффектам. И это не говоря уже о том, что он
— не ленивый,
— не чистый,
— не повторяемый,
— обрабатывает ошибки не так, как монадический bind

сделайте себе же одолжнение, займитесь своим образованием в первую очередь, а потом уже чужим

А что не относится к контексту напишите, поподробнее...

что не относится

замість 122 треба писати 42

Алло, не реагуй на подібні вкиди. Пожалій свого часу і нервів.

Коментар порушує правила спільноти і видалений модераторами.

В общем решил переписать полностью вашу реализацию. Вот моя версия:
1. Функції приготування окремих піц

// pizzas.js

export const makeDough = (name) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`${name}: Тісто готове`);
    }, 1000);
  });
};

export const addIngredients = (name) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`${name}: Інгредієнти додані`);
    }, 1000);
  });
};

export const bakePizza = (name) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`${name}: Піца готова!`);
    }, 2000);
  });
};

export const cookPizza = async (name) => {
  const dough = await makeDough(name);
  const ingredients = await addIngredients(name);
  const pizza = await bakePizza(name);
  return [dough, ingredients, pizza];
};
🔥 2. Запускаємо паралельне приготування кількох піц
// multiPizzaProcess.js
import { cookPizza } from './pizzas';

export const cookAllPizzas = () => {
  const pizzas = ['Маргарита', 'Пепероні', 'Гавайська'];

  // Одночасне приготування всіх піц
  return Promise.all(
    pizzas.map(pizzaName => cookPizza(pizzaName))
  );
};
Тут ми через Promise.all запускаємо всі запити паралельно!

🧩 3. Компонент для відображення результату

// PizzasCooking.jsx
import { use, Suspense } from 'react';
import { cookAllPizzas } from './multiPizzaProcess';

const pizzasPromise = cookAllPizzas();

function PizzasContent() {
  const pizzas = use(pizzasPromise);

  return (
    <div>
      <h2>🍕 Всі піци готові!</h2>
      {pizzas.map((steps, index) => (
        <div key={index} style={{ marginBottom: '20px' }}>
          {steps.map((step, i) => (
            <div key={i}>✅ {step}</div>
          ))}
        </div>
      ))}
      <h3>Смачного! 😋</h3>
    </div>
  );
}

function PizzasCooking() {
  return (
    <Suspense fallback={<div>⏳ Готуємо всі піци...</div>}>
      <PizzasContent />
    </Suspense>
  );
}

export default PizzasCooking;
🛡️ 4. Додаємо ErrorBoundary
(такий самий, як ми робили раніше)
// ErrorBoundary.jsx
import { Component } from 'react';

export class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  render() {
    if (this.state.hasError) {
      return <h2>🚨 Помилка: {this.state.error.message}</h2>;
    }

    return this.props.children;
  }
}
🌟 5. Головний App.jsx
// App.jsx
import { ErrorBoundary } from './ErrorBoundary';
import PizzasCooking from './PizzasCooking';

function App() {
  return (
    <div className="App">
      <h1>🍕 Мультипіцерія React 19</h1>
      <ErrorBoundary>
        <PizzasCooking />
      </ErrorBoundary>
    </div>
  );
}

export default App;
Підсумок:
Паралельне приготування кількох піц - Promise.all
Сучасний контроль за асинхронністю - Suspense + use()
Безпечне оброблення помилок - ErrorBoundary
Чистий і декларативний код Без ручних loading/error

Добавь еще LazyLoad() и будет гарнюшичка. Скажите, те кто разбираются в чем разница между defer и suspense?

Красиво съехали, знаете что нужно писать, главное не признавать...

В вашем коде есть очень большие недочеты а именно.

fetchData = (url) => {
     const controller = new AbortController();
     const signal = controller.signal;
            
     const promise = new Promise((resolve, reject) => {
          fetch(url, { signal })
              .then(response => response.json())
              .then(data => resolve(data))
              .catch(error => reject(error));
           });
           
      promise.cancel = () => {
           controller.abort();
      };
      return promise;
};

const dataPromise = fetchData('https://api.example.com/data');
     
// Скасування промісу через 1 секунду
setTimeout(() => {
     dataPromise.cancel();
}, 1000);
            
dataPromise
    .then(data => {
        console.log('Дані отримано:', data);
    })
    .catch(error => {
        console.error('Помилка:', error);
    });
1. Нельзя модифицировать стандартный Promise
Вы создаёте обычный new Promise и добавляете к нему новое поле promise.cancel = () => {}.
Это называется «promise decoration» (декорирование промиса) — очень плохая практика, потому что:

Promise по стандарту должен оставаться неизменным.

Ваш код нарушает спецификацию Promise A+ (настоящие промисы не должны иметь побочных методов типа .cancel).
Браузеры ожидают, что Promise — это объект с .then, .catch, .finally, и всё.
Расширять его опасно и нестабильно.
2. Нет обработки AbortError корректно
Когда ты вызываешь controller.abort(), в fetch генерируется специальная ошибка типа AbortError.
В вашем .catch она попадает как обычная ошибка — без специальной обработки.
Нужно явно отличать AbortError от других ошибок.
3. Можно проще через async/await
Твой код усложнён: ты руками создаёшь новый Promise вокруг fetch, хотя fetch уже возвращает Promise.
Переписал ваш код по-правильному:

const fetchData = (url, controller) => {
  const signal = controller.signal;

  return fetch(url, { signal })
    .then(response => {
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      return response.json();
    });
};

const controller = new AbortController();

const dataPromise = fetchData('https://api.example.com/data', controller);

// Скасування запиту через 1 секунду
setTimeout(() => {
  controller.abort();
}, 1000);

dataPromise
  .then(data => {
    console.log('Дані отримано:', data);
  })
  .catch(error => {
    if (error.name === 'AbortError') {
      console.error('Запит було скасовано');
    } else {
      console.error('Помилка:', error);
    }
  });
Если хотите гарно научиться нормально писать код, пишите в личку обсудим...

Душно аж в Житомирі 🥵

Нельзя модифицировать стандартный Promise

А от авторка взяла і модифікувала. Інтернет не зламався 🤷️️️️️️

Ваш код нарушает спецификацию Promise A+

Пішов читати оту специфікацію Promise A+ і щось не помітив там

не должны иметь побочных методов
Браузеры ожидают, что Promise — это объект с .then, .catch, .finally, и всё.

А до чого тут те, що там браузери очікують? 🤔

Несогласен, лучше использовать сигналы мы же в 2025 году живем! Это дает контроль над асинхронностью, например, если компонент размонтируется, обычные промисы продолжают работать в фоне, что может вызвать ошибки или утечки памяти или|и и ошибкам («Can’t perform a React state update on an unmounted component»).. Сигналы позволяют отменить запрос или процесс.
Далее, клеан код, используя сигнал, вы гарантируете, что ненужные запросы будут остановлены, а стейт не обновится после размонтирования. Огромный плюс это более отзывчивые приложения, например, пользователь меняет фильтры в интернет-магазине, и старый запрос автоматически отменяется, чтобы не мешать новому.При помощи AbortController вы можете отменить фетч-запросы (или вообще любую async-операцию), если компонент демонтирован или пользователь отменил действие.

import { useEffect, useState } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

    async function fetchUsers() {
      try {
        const response = await fetch('https://jsonplaceholder.typicode.com/users', { signal });
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        const data = await response.json();
        setUsers(data);
      } catch (err) {
        if (err.name === 'AbortError') {
          console.log('Fetch aborted');
        } else {
          setError(err.message);
        }
      }
    }

    fetchUsers();

    return () => {
      controller.abort(); // Отменяем запрос при размонтировании компонента
    };
  }, []);

  if (error) {
    return <div>Error: {error}</div>;
  }

  return (
    <div>
      {users.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

export default UserList;

Ви можете використовувати сигнали, стаття не про це.

Вы не поняли, вы просто описываете устаревшую практику использования промисов без контроля за их жизненным циклом, что является непримлимым учитывая современные тренды в веб-разработке. Кроме того, иногда еще используют когда нужно suspense, react-query, tanstack-query, swr и т.п., чтобы грамотно работать с асинхронными данными.

у вас там в 2025 все перепуталось

abort controller никак вообще не связан с промисами. это часть спеки fetch api
на жизненный цикл промиса он никак не влияет

Тут скорее всего другое имелось ввиду, что самостоятельные сырые промисы в компонентах (например, fetch().then()) без контроля это уже плохая практика написания программного кода.

промисы не могут быть сырыми/не сырыми или самостоятельными/несамостоятельными

аборт контроллер — это часть спеки фетч апи. сырыми или не сырыми могут быть запросы с помощью фетч апи, а не промисы

Термины «сырые» или «неконтролируемые» обычно используют по отношению к сетевым запросам через Fetch API, когда они выполняются напрямую в компонентах без дополнительного контроля (например, без отмены через AbortController или без обработки ошибок). AbortController — это часть спецификации Fetch API и используется для управления (отмены) fetch-запросов, а не промисов как таковых.
Может быть действительно, корректнее говорить, что плохой практикой является запуск fetch-запросов без контроля их жизненного цикла, а не использование «сырых» промисов. Промисы — это стандартный механизм работы с асинхронностью в JS, а вот запросы через fetch требуют грамотного управления, особенно в компонентах интерфейса, наверное так будет понятнее.

было бы неплохо добавить пример:

do().then(resolver1, rejecter1)
  .then(resolver2)
  .then(resolver3)
  .catch(rejecterAll)

или

do()
  .catch(rejecter1)
  .then(resolver1)
  .then(resolver2)
  .then(resolver3)
  .catch(rejecterAll);

________________

do().then(resolver1)
  .then(resolver2, rejecter2)
  .then(resolver3)
  .catch(rejecterAll)

do()
  .then(resolver1)
  .then(resolver2)
  .catch(rejecter2)
  .then(resolver3)
  .catch(rejecterAll); 

Приклад чудовий, дякую. Я його не додавала, бо не згадувала про різні способи обробки успішних і помилкових результатів безпосередньо у ланцюжках промісів.

Чудова стаття! Аналогія з піцерією дуже влучний😋

Дякую за статтю, сподобалась!

Як в людини, яка не дуже знайома із темою, виникло декілька побажань/запитань:

  1. Можливо, у першому прикладі в розділі «Ланцюжки Promise» makeDough ліпше зробити функцією, яка повертає проміс, так само, як решта кроків? Бо звуться вони всі аналогічно, як дії, а по суті цей один крок це проміс, а все решта — функції
  2. У розділі про статичні методи, ви пишете, що з методами Promise.resolve та Promise.reject ми вже познайомились. Але це не зовсім очевидно, що ми з ними знайомились. Так, ми бачили, що екзекютор функція при ініціалізації проміса приймає якісь дві функції resolve і reject, але який звʼязок між цими функціями і статичними методами Promise.resolve та Promise.reject не ясно з тексту. Можливо, ліпше або забрати речення про те, що ми вже з ними знайомились, або пояснити, як вони співвідносяться з тими функціями. А then і catch, які згадані в тому ж реченні, не статичні методи, а методи інтсансів
  3. Різниця між Promise.any Promise.race хоча й пояснена текстом, але ні приклад, ні аналогія її не демонструють до кінця. Чи переміг кухар в змаганні кухарів, якщо він першим завалив приготування піци? Можливо, ліпша аналогія була б така: якщо 5 кухарів готують піци і ми хочемо першу готову піцу, то треба використати any; якщо на піцу байдуже, а ми хочемо знати, чи хтось з кухарів вже звільнився (навіть якщо піцу вони геть спалили), тоді race?

Дякую за розгорнутий коментар 🙂

Як для людини, яка «незнайома з темою», насичено😏

«Ланцюжки Promise» makeDough

Якщо так буде легше сприймати, не бачу причини це не змінити — врахувала. Як запропонований мною перший варіант, так і оновлений (в статті приклад вже змінено) показують по суті одне й те ж саме.

Promise.resolve та Promise.reject ми вже познайомились. Але це не зовсім очевидно

Скоригувала і розширила цей блок.

then і catch, які згадані в тому ж реченні, не статичні методи, а методи інтсансів

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

Обробники — Promise Handlers
Різниця між Promise.any Promise.race хоча й пояснена текстом, але ні приклад, ні аналогія її не демонструють до кінця

Я описую наявні статичні методи і головна мета показати — а) що вони існують; б) дати їх характеристику; в) приклад застосування. Всі ці методи між собою в статті не порівнюю і не визначаю умовно, який буде кращий для застосування. Мені недокінця зрозуміло, що саме ви хочете там побачити.

Запостила і мені не сказала? Совість є? )

Простіть-пробачте!

Дякую тобі за допомогу, мотивацію та "чарівні пенделі"!😅
Стаття, мабуть, би не була б взагалі опублікована без твоєї підтримки

Корисна стаття, особливо тема з Promise Cancellation яка часто упускається в матеріалах про проміси

Насправді інформації знайшла не багато. Оскільки Promise зараз використовується досить рідко, бо є більш зручні інструменти, можливо це і є причиною.

Ех, де ви були коли в Meta Spark AR декілька років тому завезли проміси) А тепер вже й не треба, бо Meta закрила Spark AR в односторонньому порядку 🙈
Цікава стаття — трохи поностальгував за тими часами, як намагався зробити гру промісами, а потім плюнув і зробив все нодами 😅

Декілька років тому була далека від теми😅 Рада, що вам було цікаво🙂

Захотілося піци, але є декілька питань)

Ви пишете, що JavaScript однопоточний, а потім пишете про колбеки, проміси, асінк/авейт. Вони роблять JavaScript не однопоточним?

await блокує потік?

Проміси виконуються перед, між, чи після звичайного коду?

Для чого мені проміси, якщо є шаблон Monadic chaining?

Actor model, чи то патерн ООП, чи системне рішення у вигляді процесів, дозволяє обробляти асинхронні операції без callback hell, промісів та async/await.
Значить можна без промісів обійтися?

Можна додаткову статтю написати про це)

await блокує потік?

Не блокує.
JavaScript однопоточний, з аля кооперативною багатозадачністю (хост платформи)

Проміси виконуються перед, між, чи після звичайного коду?

Проміси — це передача керування поза «звичайний код»

Для чого мені проміси, якщо є шаблон Monadic chaining?

Навіщо вам, то так, невідомо.
А JavaScript вони необхідні бо він — однозадачний, але — асинхронний.

дозволяє обробляти асинхронні операції без callback hell, промісів та async/await.

Дозволяє.
Але асінхронність у однопоточній мові — набагато простіша у використанні.

Значить можна без промісів обійтися?

У однопоточній мові — ніяк.
Для реалізації асінхронного виклику у однопоточній мові треба хоч якийсь засіб кооперативної багатозадачності. Самий простий — це колбекі. Тобто коли ми прямим способом реєструємо «місце» для повернення результату асінхронного виклику.

Можна додаткову статтю написати про це)

Та їх вже писано-переписано :)
Ця тема — типова на співбесідах для мідл+ JS програміста.

Я застосував іронію Сократа для того, щоб висвітлити деякі нюанси.

І ще, проміси абсолютно не годяться в функціональному програмуванні, бо вони мутабельні.
Їх не варто відносити до кілер фічей JavaScript.

І проміси можна було б реалізувати самому, використовуючи ООП патерн, та queueMicrotask.

І ще, проміси абсолютно не годяться в функціональному програмуванні,

ну то проблеми тих маргіналів, яким треба оте функціональне программування.
Кому треба — нехай бере відповідну мову програмування.

А розмова «про Promise в JavaScript».

Їх не варто відносити до кілер фічей JavaScript.

Асінхронність у JavaScript давно його кілер фіча.
Бо для роботи з I/O її достатньо, і не треба чесного багатопотока = ускладнення мови программування.

І проміси можна було б реалізувати самому,

не маючи доступу до шедулера з мови программування — ніяк самому їх не реалізувати.
З JavaScript немає доступу.
Проміси є частиною мови, згідно специфікації:
27.2 Promise Objects
A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.
tc39.es/...​a262/#sec-promise-objects

У JavaScript є декілька таких спеціальних об’єктів, які неможливо написати на самому JS.
Reflect, Proxy, WeakRef
Promise у цьому списку.

Ці об’єкти виконують роль API до механізмів інтерпретатора і платформи виконання.

не маючи доступу до шедулера з мови программування — ніяк самому їх не реалізувати.
З JavaScript немає доступу.

Promises були до того як їх додали в специфиікацію ecma, і js версії ecma script 3/5 було достатньо з головю щоб робити такі бібліотеки(github.com/kriskowal/q) — це десь на 7 років раніше ніж їх додали в es6.

Please consider using JavaScript promises instead of Q. Native promises are faster, have better tooling support and are the future.

это буквально первое предложение в ридми

так что, да, промисы были, вот только нативной их поддержки не было по причине выше

собственно типичная граница применимости функционального программирования — врыв в джуновские статьи с инсайтом что промис — это не монада 👍

Якщо строго математично, то Promise в JavaScript не є ідеальною монадою в сенсі теорії категорій, хоча має багато схожих характеристик і часто розглядається як монадоподібна структура, тому що метод then дозволяє послідовно компонувати асинхронні операції, передаючи результат попередньго промісу у наступний, тому це дуже схоже на операцію bind у монадах. Можливо ви мали на увазі, трохи інше,що Promise капсулює асинхронний контекст, дозволяючи вам працювати зі значеннями, які ще не доступні, у спосіб, що нагадує роботу з монадами.

Ви пишете, що JavaScript однопоточний

Пишу про цю характеристику для контексту — щоб дати розуміти:
а) чому Promise виник як інструмент;
б) які проблеми може вирішити в контексті однопоточності.

а потім пишете про колбеки, проміси, асінк/авейт

Пишу, оскільки я наводжу приклади методів асинхронного програмування — це знову таки про контекст.

Вони роблять JavaScript не однопоточним?

Ці інструменти дозволяють виконувати кілька операцій (якщо дуже спрощено) паралельно. Це не змінює вказану характеристику мови програмування. Та і вони не повинні змінювати, основна мета — вирішити завдання одночасного виконання кількох задач.

Проміси виконуються перед, між, чи після звичайного коду?

Виконуються після синхронного коду, але до макрозавданнь на кшталт setTimeout.

Для чого мені проміси

Не пригадую, щоб я писала, що цей інструмент обовʼязково використовувати читачеві.

якщо є шаблон Monadic chaining?

Нагадаю, що стаття буквально «... про Promise в JavaScript ...». Це стосується і зауважень щодо

Actor model

.

Значить можна без промісів обійтися?

Ви хочете в статті про Promise читати про патерни? Це зміщує фокус з головної ідеї статті, як на мене. Якби я хотіла написати про інший інструмент, стаття мала б іншу назву і контекст.

Про тему наступної статті ще думаю, дякую за ваш коментар. Він, до речі, задублювався.

await блокує потік?

Для простого розуміння, await дає змогу послідовно очікувати результат без блокування потоку, додаючи в чергу. При тому слід розуміти, що процеси які виконуються з тої черги, в момент виконування все одно блокують потік.

Захотілося піци, але є декілька питань)

Ви пишете, що JavaScript однопоточний, а потім пишете про колбеки, проміси, асінк/авейт. Вони роблять JavaScript не однопоточним?

await блокує потік?

Проміси виконуються перед, між, чи після звичайного коду?

Для чого мені проміси, якщо є шаблон Monadic chaining?

Actor model, чи то патерн ООП, чи системне рішення у вигляді процесів, дозволяє обробляти асинхронні операції без callback hell, промісів та async/await.
Значить можна без промісів обійтися?

Можна додаткову статтю написати про це)

Дякую! Дуже змістовна стаття.

Написати статтю, щоб краще розібратися в темі, — це дуже зріле та ефективне рішення, яке покращує вміння формулювати думки й допомагає усвідомленіше читати статті інших авторів.

Трохи критики:
1. Частину з promise.cancel я б переписав, бо рішення виглядає як хак. Натомість я б повертав [promise, controller.abort].
2. Якщо покрити код тестами, можна ще глибше розібратися в темі.
3. Вже є нові ключові слова async та await.

Чекаю на продовження і нові технічні статті від тебе!

Дякую за розгорнутий коментар, продовження в планах на найближче майбутнє.

Расширять промис методом cancel — антипаттерн. Лучше возвращать промис и функцию отмены отдельно, либо в массиве, либо в объекте. Согласен,возвращение [promise, controller.abort] — это современный,подход, но я бы сделал интерфейс ещё более понятным, для этого можно возвращать объект:return { promise, cancel: () => controller.abort() }; или такое, это улучшит читаемость и расширяемость

const { promise, cancel } = fetchWithAbort('...');

Підписатись на коментарі