Чи відхиляє Обіцянку лише у випадках помилок?


25

Скажімо, у мене є ця функція автентифікації, яка повертає обіцянку. Потім обіцянка вирішується з результатом. Хибні і правдиві очікувані результати, як я бачу, і відхилення мають відбуватися лише у випадку помилки. Або, чи невдача в аутентифікації вважається чимось, від чого ви відкинете обіцянку?


Якщо аутентифікація не вдалася, ви повинні rejectі не повертати помилкове, але якщо ви очікуєте, що значення буде a Bool, то ви мали успіх і вам слід вирішити з Bool незалежно від значення. Обіцяння - це свого роду проксі-сервери для значень - вони зберігають повернене значення, тож лише якщо ви не зможете отримати це значення reject. Інакше слід resolve.

Це гарне запитання. Це стосується однієї з невдач дизайну обіцянки. Існує два типи помилок, очікувані збої, наприклад, коли користувач надає поганий вхід (наприклад, невхід у систему) та несподівані збої, які є помилками в коді. Дизайн обіцянок об'єднує дві концепції в один потік, що ускладнює їх розрізнення.
zzzzBov

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

4
інший спосіб подумати над цим - якби це був виклик синхронного методу, чи трактуєте б ви регулярну помилку аутентифікації (неправильне ім’я користувача / пароль) як повернення falseабо як викид винятку?
wrschneider

2
Fetch API є хорошим прикладом цього. Це завжди спрацьовує, thenколи сервер відповідає - навіть якщо код помилки повернуто - і ви повинні перевірити response.ok. catchОброблювач спрацьовує тільки для несподіваних помилок.
CodingIntrigue

Відповіді:


22

Гарне питання! Важкої відповіді немає. Це залежить від того, що ви вважаєте винятковим у цій конкретній точці потоку .

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

  1. Помилка аутентифікації повинен , тому що абонент чекає об'єкт у відповідь, і все інше є винятком з цього потоку.rejectPromiseUser

  2. Не вдалося resolveвстановити автентифікацію Promise, хоча nullце не так, оскільки надання неправильних облікових даних насправді не є винятковим випадком, і абонент не повинен сподіватися, що потік завжди призведе до а User.

Зауважте, що я розглядаю проблему з боку абонента . У потоці інформації очікує, що викликає абонент, що його дії призведуть до User(а все інше - помилка), чи має сенс цей конкретний абонент обробляти інші результати?

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

  • HTTP-шар говорить RESOLVE! Запит було надіслано, сокет чисто закрився, і сервер видав дійсну відповідь. Fetch API робить це.
  • Потім протокольний шар каже REJECT! Код статусу у відповіді становив 401, що нормально для HTTP, але не для протоколу!
  • Шар аутентифікації каже НІ, РЕШИТИ! Він виявляє помилку, оскільки 401 є очікуваним статусом для неправильного пароля і вирішує для nullкористувача.
  • Інтерфейсний контролер говорить НІЧОГО НЕ ТАКОГО, ВІДТВЕРЖАЙТЕ! Модальне відображення на екрані очікувало ім'я користувача та аватар, і будь-що, крім цієї інформації, є помилкою в цей момент.

Цей 4-бальний приклад, очевидно, складний, але він ілюструє 2 бали:

  1. Буде щось винятком / відхиленням чи ні, залежить від навколишнього потоку та очікувань
  2. Різні шари вашої програми можуть по-різному трактувати один і той же результат, оскільки вони розташовані на різних етапах потоку

Тож знову, жодної важкої відповіді. Час задуматися та замислити!


6

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

data Either x y = Left x | Right y

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

Причина полягає в тому, що JS візьме throwзаяви з коду обробки обіцянок і також додасть їх у Leftсторону. Технічно в JS ви можете throwвсе, включаючи true / false або рядок або число: але код JavaScript також викидає речі безthrow (коли ви робите такі дії, як намагання отримати доступ до властивостей на нулях), і для цього є об’єднаний API ( Errorоб'єкт) . Тож, коли ви добираєтеся до лову, зазвичай приємно мати можливість припускати, що ці помилки є Errorоб'єктами. А оскільки rejectобіцянка агломерується у будь-яких помилках будь-якого з перерахованих вище помилок, ви, як правило, бажаєте лише throwінших помилок, щоб ваш catchвислів мав просту, послідовну логіку.

Тому, хоча ви можете поставити if-conditional у своєму catchі шукати помилкові помилки, у цьому випадку випадок істини є тривіальним,

Either (Either Error ()) ()

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

Either Error Bool

Насправді наступний рівень логіки автентифікації, ймовірно, повертає якийсь Userоб’єкт, що містить аутентифікованого користувача, так що це стає:

Either Error (Maybe User)

і це більш-менш те, що я очікував: поверніть nullу випадку, коли користувач не визначений, інакше поверніть {user_id: <number>, permission_to_launch_missiles: <boolean>}. Я б очікував, що загальний випадок не входу в систему вдається виправити, наприклад, якщо ми знаходимося в якомусь режимі "демонстрація новим клієнтам", і не слід змішуватися з помилками, куди я випадково зателефонував, object.doStuff()коли object.doStuffбув undefined.

Тепер з цим сказав, що ви можете зробити , це визначити NotLoggedInабо PermissionErrorвиключення , яке походить від Error. Потім у речах, які справді потребують цього, ви хочете написати:

function launchMissiles() {
    function actuallyLaunchThem() {
        // stub
    }
    return getAuth().then(auth => {
        if (auth === null) {
            throw new PermissionError('Cannot launch missiles without permission, cannot have permission if not logged in.');
        } else if (auth.permission_to_launch_missiles) {
            return actuallyLaunchThem();
        } else {
            throw new PermissionError(`User ${auth.user_id} does not have permission to launch the missiles.`);
        }
    });
}

3

Помилки

Поговоримо про помилки.

Існує два типи помилок:

  • очікувані помилки
  • несподівані помилки
  • помилки один за одним

Очікувані помилки

Очікувані помилки - це стани, коли трапляється неправильна річ, але ви знаєте, що це може статися, тому ви вирішуєте це.

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

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

Несподівані помилки

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

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

try..catch

Поговоримо try..catch.

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

function example(param) {
  if (!Array.isArray(param) {
    throw new TypeError('"param" should be an array!');
  }
  ...
}

Через це, try..catchблоки теж не так часто зустрічаються для потоку управління. Зазвичай досить легко додати кілька перевірок перед викликом методів, щоб уникнути очікуваних помилок.

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

try..catchне повинно бути рідкістю. Є кілька приємних випадків використання, які є більш поширеними в таких мовах, як Java та C #. Java та C # мають перевагу введених catchконструкцій, так що ви можете розмежувати очікувані та несподівані помилки:

C # :
try
{
  var example = DoSomething();
}
catch (ExpectedException e)
{
  DoSomethingElse(e);
}

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

У JavaScript цю конструкцію можна реплікувати за допомогою:

try {
  let example = doSomething();
} catch (e) {
  if (e instanceOf ExpectedError) {
    DoSomethingElse(e);
  } else {
    throw e;
  }
}

Не такий елегантний, що є частиною причини, чому це нечасто.

Функції

Поговоримо про функції.

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

Наприклад, authenticate()може автентифікувати користувача.

Це може бути записано як:

const user = authenticate();
if (user == null) {
  // keep doing stuff
} else {
  // handle expected error
}

Крім того, він може бути записаний так:

try {
  const user = authenticate();
  // keep doing stuff
} catch (e) {
  if (e instanceOf AuthenticationError) {
    // handle expected error
  } else {
    throw e;
  }
}

Обидва прийнятні.

Обіцянки

Поговоримо про обіцянки.

Обіцяння - це асинхронна форма try..catch. Зателефонувавши new Promiseабо Promise.resolveзапустивши свій tryкод. Зателефонував throwабо Promise.rejectнадішле вам catchкод.

Promise.resolve(value)   // try
  .then(doSomething)     // try
  .then(doSomethingElse) // try
  .catch(handleError)    // catch

Якщо у вас є асинхронна функція для автентифікації користувача, ви можете написати це як:

authenticate()
  .then((user) => {
    if (user == null) {
      // keep doing stuff
    } else {
      // handle expected error
    }
  });

Крім того, він може бути записаний так:

authenticate()
  .then((user) => {
    // keep doing stuff
  })
  .catch((e) => {
    if (e instanceOf AuthenticationError) {
      // handle expected error
    } else {
      throw e;
    }
  });

Обидва прийнятні.

Гніздування

Поговоримо про гніздування.

try..catchможна вкласти. Ваш authenticate()метод може мати внутрішній try..catchблок, такий як:

try {
  const credentials = requestCredentialsFromUser();
  const user = getUserFromServer(credentials);
} catch (e) {
  if (e instanceOf CredentialsError) {
    // handle failure to request credentials
  } else if (e instanceOf ServerError) {
    // handle failure to get data from server
  } else {
    throw e; // no idea what happened
  }
}

Так само обіцянки можуть вкладатись. Ваш authenticate()метод асинхронізації може внутрішньо використовувати обіцянки:

requestCredentialsFromUser()
  .then(getUserFromServer)
  .catch((e) => {
    if (e instanceOf CredentialsError) {
      // handle failure to request credentials
    } else if (e instanceOf ServerError) {
      // handle failure to get data from server
    } else {
      throw e; // no idea what happened
    }
  });

То яка відповідь?

Гаразд, я думаю, що саме час мені реально відповісти на питання:

Чи невдача в аутентифікації вважається чимось, від чого ви відкинете обіцянку?

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

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

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


0

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


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

0

Поводження з обіцянкою більш-менш схоже на стан "якщо". Ви вирішуєте, чи хочете ви "вирішити" чи "відхилити", якщо аутентифікація не вдалася.


1
обіцянка - асинхронна try..catch, ні if.
zzzzBov

@zzzBox, тож за цією логікою ви повинні використовувати Обіцянку як асинхронну try...catchі просто сказати, що якщо вам вдалося виконати і отримати результат, ви повинні вирішити незалежно від отриманого значення, інакше слід відхилити?

@ щось щось ні, ви неправильно пояснили мій аргумент. try { if (!doSomething()) throw whatever; doSomethingElse() } catch { ... }ідеально чудово, але конструкція, яка Promiseпредставляє, - це try..catchчастина, а не ifчастина.
zzzzBov

@zzzzBov Я це зрозумів на честь :) Мені подобається аналогія. Але моя логіка полягає лише в тому, що якщо doSomething()не вдасться, то вона кинеться, але якщо ні, то вона може містити потрібне вам значення (ваше ifвище трохи збиває з пантелику, оскільки це не є частиною вашої ідеї тут :)). Ви повинні відхилити лише якщо є причина кинути (за аналогією), так що якщо тест не вдався. Якщо тест вдався, ви завжди повинні вирішувати, незалежно від того, чи є його значення позитивним, правда?

@ щось там я вирішив написати відповідь (припустимо, що це залишається відкритим досить довго), оскільки коментарів недостатньо для висловлення моїх думок.
zzzzBov
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.