9 багатообіцяльних порад щодо промісів

10 хв. читання

1. Ви можете повернути проміс всередині .then

Дозвольте мені виділити найважливішу пораду:

Так! Ви можете повернути проміс всередині .then

Також, проміс, що повертається, автоматично розпаковується в наступному .then

.then(r => {
    return serverStatusPromise(r); // це проміс { statusCode: 200 }
})
.then(resp => {
    console.log(resp.statusCode); // 200; зверніть увагу на автоматичне розпакування проміса
})

2. Ви створюєте новий проміс кожен раз, коли виконуєте .then

Якщо ви знайомі з JavaScript стилем dot chaining, то будете відчувати себе як вдома. Але для новачка його використання може бути неочевидним.

Кожного разу, коли ви використовуєте .then чи .catch, то створюєте новий проміс. Цей проміс — поєднання промісу, який ви тільки-но «прикули» (chained), і щойно прикріпленого .then / .catch.

Приклад:

var statusProm = fetchServerStatus();

var promA = statusProm.then(r => (r.statusCode === 200 ? "good" : "bad"));

var promB = promA.then(r => (r === "good" ? "ALL OK" : "NOTOK"));

var promC = statusProm.then(r => fetchThisAnotherThing());

Зв'язки вищезазначених промісів можна чітко описати за допомогою блок-схеми:

9 багатообіцяльних порад щодо промісів

Тут важливо відзначити, що promA, promB та promC — різні, але пов'язані між собою проміси.

Якщо promB не виконається, жоден інший вузол не буде зачеплений, але якщо не виконається statusProm, то це зачепить всі вузли, тобто rejected.

3. Проміс є resolved/rejected для ВСІХ

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

Це також означає, що ніхто не може змінити ваш проміс, тому, будь ласка, не соромтеся передавати його по колу без зайвих турбот.
function yourFunc() {
  const yourAwesomeProm = makeMeProm();

  yourEvilUncle(yourAwesomeProm); // будьте впевнені, що ваш проміс буде працювати, незалежно від того, як злий дядько користується ним 

  return yourAwesomeProm.then(r => importantProcessing(r));
}

function yourEvilUncle(prom) {
  return prom.then(r => Promise.reject("destroy!!")); // ваш злий дядько
}

У наведеному вище прикладі ви можете побачити, що проміс навмисно для всіх ускладнює можливість здійснювати різні підлі речі. Як я вище зазначив: Зберігайте спокій і передавайте проміс по колу.

4. Конструктор промісів — не рішення

Я бачив як товариші розробники повсюдно використовували стиль конструктора, думаючи, що вони роблять це «промісним» способом. Але це велика брехня. Фактична причина полягає в тому, що API конструктора дуже схожий на старий добрий API зворотного виклику, а старі звички дуже живучі.

Якщо ви виявите, що повсюди пишете Promise constructors, то ви робите це неправильно!

Щоб насправді зробити крок вперед й відійти від зворотних викликів, вам потрібно звести до мінімуму кількість використовуваних конструкторів промісів.

Перейдімо до актуального варіанту використання Promise constructor:

return new Promise((res, rej) => {
  fs.readFile("/etc/passwd", function(err, data) {
    if (err) return rej(err);
    return res(data);
  });
});

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

Подивімося на надмірне використання Promise constructor:

☠️ Неправильно

return new Promise((res, rej) => {
    var fetchPromise = fetchSomeData(.....);
    fetchPromise
        .then(data => {
            res(data); // неправильно!!!
        })
        .catch(err => rej(err))
})

💖 Правильно

return fetchSomeData(...); // коли це виглядає правильним, воно і є правильним!

Обгортання промісу за допомогою Promise constructor просто надлишкове й знищує ціль використання промісу як такого.

😎 Порада від професіонала

Якщо ви завзятий користувач nodejs, я рекомендую вам переглянути util.promisify. Ця крихітна річ допоможе вам конвертувати ваш зворотний виклик в node.js стилі у проміс.

const {promisify} = require('util');
const fs = require('fs');

const readFileAsync = promisify(fs.readFile);

readFileAsync('myfile.txt', 'utf-8')
  .then(r => console.log(r))
  .catch(e => console.error(e));

5. Використовуйте Promise.resolve

Javascript надає Promise.resolve, який використовується для скороченого написання чогось на зразок цього:

var similarProm = new Promise(res => res(5));
// ^^ що еквівалентно цьому
var prom = Promise.resolve(5);

Він має безліч варіантів використання і є моїм улюбленим способом конвертації звичайного (синхронного) об'єкта javascript у проміс.

// конвертує синхронну функцію в асинхронну
function foo() {
  return Promise.resolve(5);
}

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

function goodProm(maybePromise) {
  return Promise.resolve(maybePromise);
}

goodProm(5).then(console.log); // 5

var sixPromise = fetchMeNumber(6); // це проміс, який розкладається у число 5

goodProm(sixPromise).then(console.log); // 6

goodProm(Promise.resolve(Promise.resolve(5))).then(console.log); // 5, зверніть увагу, він автоматично розпаковує всі шари промісів!

6. Використовуйте Promise.reject

Javascript також надає Promise.reject для скороченого написання:

var rejProm = new Promise((res, reject) => reject(5));

rejProm.catch(e => console.log(e)) // 5

Одним з моїх улюблених варіантів використання є раннє відхилення за допомогою Promise.reject.

function foo(myVal) {
    if (!mVal) {
        return Promise.reject(new Error('myVal is required'))
    }
    return new Promise((res, rej) => {
        // ваша велика конвертація зворотнього виклику у проміс!
    })
}

Простіше кажучи, використовуйте Promise.reject всюди, де ви хочете відхилити проміс.

У наведеному нижче прикладі я використовую його всередині .then:

.then(val => {
  if (val != 5) {
    return Promise.reject('Not Good');
  }
})
.catch(e => console.log(e)) // Not Good

Примітка: Ви можете помістити будь-яке значення всередину Promise.reject, так само як і в Promise.resolve. Причина, через яку ви часто виявляєте Error у відхиленому промісі, полягає в тому, що він найчастіше використовується для того, щоб видати асинхронну помилку.

7. Використовуйте Promise.all

Також Javascript надає Promise.all.

У псевдокоді алгоритм Promise.all можна підсумувати так:

Бере масив промісів
	потім чекає поки всі вони закінчать
	потім повертає новий проміс, який розкладається у масив 
	відловлює, якщо хоча б один з них не виконується/відхиляється

Наступний приклад показує, коли успішно виконуються всі проміси:

var prom1 = Promise.resolve(5);
var prom2 = fetchServerStatus(); // повертає проміс {statusCode: 200}

Proimise.all([prom1, prom2])
.then([val1, val2] => { // відмітьте, що він розкладається у масив
    console.log(val1); // 5
    console.log(val2.statusCode); // 200
})

Цей показує, коли один з них не виконується:

var prom1 = Promise.reject(5);
var prom2 = fetchServerStatus(); // повертає проміс {statusCode: 200}

Proimise.all([prom1, prom2])
.then([val1, val2] => {
    console.log(val1); 
    console.log(val2.statusCode); 
})
.catch(e =>  console.log(e)) // 5, переходить прямо дo .catch

Примітка: Promise.all дуже розумний! У разі відмови, він не чекає виконання всіх промісів! Кожного разу, коли будь-який проміс відхиляється, він негайно переривається, не чекаючи виконання інших промісів.

😎 Порада від професіонала

Promise.all не надає спосіб виконання промісів у пакетах (багатонитковість), оскільки за задумом проміси виконуються в момент їх створення. Якщо ви хочете контролювати виконання, я рекомендую спробувати Bluebird.map.

8. Не бійтеся відмови АБО не додавайте зайвих .catch після кожного .then

Як часто ми боїмося, що помилки спливуть десь посередині?

Щоб подолати цей страх, ось дуже проста порада:

Зробіть обробку відмов проблемою функції предка

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

Не бійтеся писати щось на зразок цього:

return fetchSomeData(...);

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

💘 Дозвіл відхилення

Дозвіл відхилення є простим — що б ви не повернули у .catch, припускається, що воно буде дозволене. Однак, існує виверт — якщо ви повернете Promise.reject у .catch, то проміс буде відхилений.

.then(() => 5.length) // <-- тут сталося щось неправильне
.catch(e => {
        return 5;  // <-- знову робить javascript величним
})
.then(r => {
    console.log(r); // 5
})
.catch(e => {
    console.error(e); // ця функція ніколи не буде викликана :)
})

💔 Відмова відхилення

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

Важливо пам'ятати, як тільки ви напишете catch, це означатиме, що ви обробляєте помилку. Це схоже на роботу синхронних try/catch.

Якщо ви справді хочете перехопити відмову: (я наполегливо рекомендую цього не робити!)

.then(() => 5.length) // <-- тут відбувається щось неправильне
.catch(e => {
  errorLogger(e); // зробіть щось нечисте
  return Promise.reject(e); // відхиліть його. Так, ви можете це зробити!
})
.then(r => {
    console.log(r); // цей .then (або будь-який наступний .then) ніколи не буде викликаний, оскільки ми його відхилили вище :)
})
.catch(e => {
    console.error(e); //<-- це стає проблемою цього catch
})

Тонка грань між .then(x,y) та then(x).catch(x)

.then приймає другий параметр зворотнього виклику, який також може бути використаний для обробки помилок. Це може виглядати схожим на те, якби ви написали then(x).catch(x), але обидва ці обробники помилок відрізняються тим, яку помилку вони відловлюють.

Я дозволю наведеному нижче прикладу говорити самому за себе.

.then(function() {
   return Promise.reject(new Error('помилка'));
}).catch(function(e) {
   console.error(e); // помилка
});


.then(function() {
   return Promise.reject(new Error('something wrong happened'));
}, function(e) { // зворотній виклик обробляє помилку, яка надходить з ланцюжка поверх поточного .then
    console.error(e); // не зареєстровано жодної помилки
});

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

Примітка: в 99.9% випадках вам краще використовувати простіший then(x).catch(x).

9. Уникайте пекла .then

Ця порада дуже проста — намагайтесь уникати .then всередині .then або .catch. Повірте мені, цього можна уникати частіше, ніж ви думаєте.

☠️ Неправильно

request(opts)
.catch(err => {
  if (err.statusCode === 400) {
    return request(opts)
           .then(r => r.text())
           .catch(err2 => console.error(err2))
  }
})

💖 Правильно

request(opts)
.catch(err => {
  if (err.statusCode === 400) {
    return request(opts);
  }
  return Promise.reject(err);
})
.then(r => r.text())
.catch(err => console.erro(err));

Іноді трапляється, що нам потрібно декілька змінних в області видимості .then, і немає іншого вибору, окрім як створення ще одного ланцюга .then.

.then(myVal => {
    const promA = foo(myVal);
    const promB = anotherPromMake(myVal);
    return promA
          .then(valA => {
              return promB.then(valB => hungryFunc(valA, valB)); // дуже голодна!
          })
})

Я рекомендую для порятунку від цього використовувати деструктуруючу силу ES6, змішану з Promise.all!

.then(myVal => {
    const promA = foo(myVal);
    const promB = anotherPromMake(myVal);
    return Promise.all([prom, anotherProm])
})
.then(([valA, valB]) => {   // деструктуризація ES6 піде на добру справу
    console.log(valA, valB) // всі дозволені значення
    return hungryFunc(valA, valB)
})

Примітка: Для вирішення цієї проблеми ви також можете використовувати async/await, якщо ваш вузол/браузер/бос/свідомість дозволить!

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 6.2K
Приєднався: 7 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація