Отклонение обещания только для случаев ошибки?

25

Допустим, у меня есть функция аутентификации, которая возвращает обещание. Обещание затем разрешается с результатом. Ложь и истина - это ожидаемые результаты, на мой взгляд, и отклонения должны происходить только в случае ошибки. Или, считается ли сбой в аутентификации чем-то, от чего вы бы отказались?

Матье Бертин
источник
Если аутентификация не удалась, вы должны rejectи не возвращать false, но если вы ожидаете, что значение будет a Bool, то вы добились успеха, и вы должны решить с Bool независимо от значения. Обещания являются своего рода прокси для значений - они хранят возвращаемое значение, поэтому только если вы не можете получить это значение reject. В противном случае вы должны resolve.
Это хороший вопрос. Это касается одного из недостатков обещанного дизайна. Существует два типа ошибок: ожидаемые сбои, например, когда пользователь вводит неверные данные (например, не удается войти в систему), и непредвиденные сбои, которые являются ошибками в коде. Проект обещаний объединяет две концепции в единый поток, что затрудняет их различение для обработки.
zzzzBov
1
Я бы сказал, что разрешение означает использование ответа и продолжение вашего приложения, а отклонение означает отмену текущей операции (и, возможно, попытаться еще раз или сделать что-то еще).
4
Другой способ думать об этом - если бы это был синхронный вызов метода, вы бы восприняли обычную ошибку аутентификации (неверное имя пользователя / пароль) как возвращение falseили как исключение?
wrschneider
2
Fetch API является хорошим примером этого. Он всегда срабатывает, thenкогда сервер отвечает - даже если возвращается код ошибки - и вы должны проверить response.ok. catchОбработчик срабатывает только для неожиданных ошибок.
CodingIntrigue

Ответы:

22

Хороший вопрос! Трудного ответа нет. Это зависит от того, что вы считаете исключительным в этой конкретной точке потока .

Отклонение - Promiseэто то же самое поднятие исключения. Не все нежелательные результаты являются исключительными , результат ошибок . Вы можете утверждать ваше дело в обоих направлениях:

  1. Ошибка аутентификации долженrejectPromise , потому что абонент ожидает Userобъект в ответ, и все остальное является исключением из этого потока.

  2. Ошибка аутентификация должна , хотя и , поскольку предоставление неправильных учетных данных не действительно исключительноеresolvePromisenull случай, и вызывающий абонент не должен ожидать , что поток всегда приводит в User.

Обратите внимание, что я смотрю на проблему со стороны звонящего . В потоке информации, ожидает ли звонящий его действия приведут к User(и что-либо еще является ошибкой), или имеет ли смысл для этого конкретного вызывающего обрабатывать другие результаты?

В многослойной системе ответ может меняться по мере прохождения данных через слои. Например:

  • Слой HTTP говорит РАЗРЕШИТЬ! Запрос отправлен, сокет закрыт, и сервер отправил правильный ответ. Fetch API делает это.
  • Уровень протокола тогда говорит ОТКЛОНЕН! Код состояния в ответе был 401, что хорошо для HTTP, но не для протокола!
  • Уровень аутентификации говорит НЕТ, РАЗРЕШИТЬ! Это ловит ошибку, так как 401 - ожидаемое состояние для неправильного пароля, и разрешается nullпользователю.
  • Интерфейс контроллера говорит, что ничего, отвергни! Модальное отображение на экране ожидало имя пользователя и аватара, и что-либо, кроме этой информации, является ошибкой на этом этапе.

Этот пример с 4 пунктами, очевидно, сложен, но он иллюстрирует 2 пункта:

  1. Является ли что-то исключением / отклонением или нет, зависит от окружающего потока и ожиданий
  2. Различные слои вашей программы могут обрабатывать один и тот же результат по-разному, поскольку они расположены на разных этапах процесса.

Итак, еще раз, нет жесткого ответа. Время подумать и спроектировать!

slezica
источник
6

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

data Either x y = Left x | Right y

Теперь вы действительно замечаете, что тип на левой стороне неоднозначен для обещаний; Вы можете отказаться с чем угодно. Это верно, потому что JS слабо типизирован, но вы должны быть осторожны, если вы программируете в обороне.

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

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

Если ваш поток управления проще, отклонив обещание, а затем проверив типы ошибок в коде обработки ошибок, сделайте это вместо этого.

zzzzBov
источник
0

Я использовал ветку «отклонить» в Promise для представления действия «отмена» диалоговых окон пользовательского интерфейса jQuery. Это казалось более естественным, чем использование ветви «resol», не в последнюю очередь потому, что в диалоговом окне часто есть несколько вариантов «close».

Альнитак
источник
Большинство пуристов, которых я знаю, с тобой не согласятся.
0

Выполнение обещания более или менее похоже на условие «если». Вам решать, хотите ли вы «разрешить» или «отклонить», если аутентификация не удалась.

evilReiko
источник
1
обещание асинхронное try..catch, нет if.
zzzzBov
@zzzBox, поэтому по этой логике вы должны использовать Promise в качестве асинхронного try...catchи просто сказать, что если вы смогли выполнить и получить результат, вы должны разрешить независимо от полученного значения, в противном случае вы должны отклонить?
@ что-то здесь, нет, ты неправильно истолковал мой аргумент. try { if (!doSomething()) throw whatever; doSomethingElse() } catch { ... }прекрасно, но конструкция, которую Promiseпредставляет собой, является try..catchчастью, а не ifчастью.
zzzzBov
@zzzzBov Я понял это по справедливости :) Мне нравится аналогия. Но моя логика заключается в том, что если doSomething()не получится, то он выдаст, но если нет, он может содержать значение, которое вам нужно (ваше ifприведенное выше немного сбивает с толку, поскольку это не является частью вашей идеи здесь :)). Вы должны только отклонить, если есть причина бросить (в аналогии), так, если тест не удался. Если тест прошел успешно, вы всегда должны решить, независимо от того, является ли его значение положительным, верно?
@ что-то здесь, я решил написать ответ (при условии, что он остается открытым достаточно долго), потому что комментариев недостаточно для выражения моих мыслей.
zzzzBov