Обещания JavaScript - отклонить против броска


385

Я прочитал несколько статей на эту тему, но мне все еще не ясно, есть ли разница между Promise.rejectвыдачей ошибки. Например,

Использование Promise.reject

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            return Promise.reject(new PermissionDenied());
        }
    });

Используя бросок

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            throw new PermissionDenied();
        }
    });

Я предпочитаю использовать throwпросто потому, что он короче, но мне было интересно, есть ли преимущество одного над другим.


9
Оба метода дают одинаковый ответ. .then()Обработчик ловит брошенное исключение и превращает его в отклоненном обещание автоматически. Поскольку я читал, что сгенерированные исключения не особенно быстро выполняются, я предполагаю, что возвращение отклоненного обещания может быть выполнено немного быстрее, но вам придется разработать тест в нескольких современных браузерах, если это важно знать. Я лично использую, throwпотому что мне нравится удобочитаемость.
jfriend00

@webduvet не с обещаниями - они предназначены для работы с броском.
joews

15
Недостатком throwявляется то, что это не приведет к отклонению обещания, если оно будет выдано из асинхронного обратного вызова, такого как setTimeout. jsfiddle.net/m07van33 @ Blondie ваш ответ был правильным.
Кевин Б.

@ Joews это не значит, что это хорошо;)
webduvet

1
Ах, правда. Таким образом, пояснение к моему комментарию было бы «если бы он был брошен из асинхронного обратного вызова, который не был обещан » . Я знал, что есть исключение, я просто не мог вспомнить, что это было. Я тоже предпочитаю использовать throw просто потому, что считаю его более читабельным и позволяет мне исключить rejectего из списка параметров.
Кевин Б.

Ответы:


346

Нет преимущества в использовании одного против другого, но есть особый случай, когда throwон не будет работать. Тем не менее, эти случаи могут быть исправлены.

Каждый раз, когда вы находитесь внутри обещания обратного вызова, вы можете использовать throw. Однако, если вы находитесь в любом другом асинхронном обратном вызове, вы должны использовать reject.

Например, это не сработает:

new Promise(function() {
  setTimeout(function() {
    throw 'or nah';
    // return Promise.reject('or nah'); also won't work
  }, 1000);
}).catch(function(e) {
  console.log(e); // doesn't happen
});

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

  1. используя функцию отклонения исходного Promise в течение тайм-аута:

new Promise(function(resolve, reject) {
  setTimeout(function() {
    reject('or nah');
  }, 1000);
}).catch(function(e) {
  console.log(e); // works!
});

  1. обещая тайм-аут:

function timeout(duration) { // Thanks joews
  return new Promise(function(resolve) {
    setTimeout(resolve, duration);
  });
}

timeout(1000).then(function() {
  throw 'worky!';
  // return Promise.reject('worky'); also works
}).catch(function(e) {
  console.log(e); // 'worky!'
});


54
Стоит упомянуть, что вы не можете использовать те места внутри неопознанного асинхронного обратного вызова, которые вы не можете использовать throw error, и return Promise.reject(err)это то, о чем ОП просил нас сравнить. Именно поэтому вы не должны помещать асинхронные обратные вызовы в обещания. Пообещайте все, что асинхронно, и тогда у вас нет этих ограничений.
jfriend00

9
«Однако, если вы используете любой другой вид обратного вызова», на самом деле должно быть «Однако, если вы используете какой-либо другой вид асинхронного обратного вызова». Обратные вызовы могут быть синхронными (например, с Array#forEach) и с ними, бросание внутри них будет работать.
Феликс Сапарелли

2
@KevinB читая эти строки «есть особый случай, когда бросок не сработает». и «Каждый раз, когда вы находитесь внутри обратного вызова, вы можете использовать throw. Однако, если вы используете любой другой асинхронный обратный вызов, вы должны использовать reject». У меня такое ощущение, что примеры фрагментов покажут случаи, когда throwони не будут работать, а вместо этого Promise.reject- лучший выбор. Однако фрагменты не зависят от любого из этих двух вариантов и дают одинаковый результат независимо от того, что вы выберете. Я что-то пропустил?
Аншул

2
да. если вы используете throw в setTimeout, то catch не будет вызван. Вы должны использовать тот, rejectкоторый был передан new Promise(fn)обратному вызову.
Кевин Б.

2
@KevinB спасибо, что остались. Пример, приведенный ОП, упоминает, что он специально хотел сравнить return Promise.reject()и throw. Он не упоминает rejectобратный вызов, данный в new Promise(function(resolve, reject))конструкции. Таким образом, хотя ваши два фрагмента правильно демонстрируют, когда вам следует использовать обратный вызов разрешения, вопрос OP был не в этом.
Anshul

202

Другим важным фактом является то, что reject() НЕ завершает поток управления, как returnоператор. В отличие от throwэтого прекращается контроль потока.

Пример:

new Promise((resolve, reject) => {
  throw "err";
  console.log("NEVER REACHED");
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));

против

new Promise((resolve, reject) => {
  reject(); // resolve() behaves similarly
  console.log("ALWAYS REACHED"); // "REJECTED" will print AFTER this
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));


51
Ну, суть верна, но сравнение сложно. Потому что обычно вы должны вернуть отклоненное обещание в письменной форме return reject(), чтобы следующая строка не выполнялась.
А-Я.

7
Почему вы хотите вернуть его?
Luker

31
В данном случае return reject()это просто сокращение, reject(); returnт. Е. То, что вы хотите - это завершить поток. Возвращаемое значение исполнителя (переданной функции new Promise) не используется, поэтому это безопасно.
Феликс Сапарелли

47

Да, самое большое отличие в том, что reject - это функция обратного вызова, которая выполняется после того, как обещание отклонено, тогда как throw не может использоваться асинхронно. Если вы решили использовать отклонение, ваш код продолжит работать в обычном асинхронном режиме, в то время как throw будет определять приоритет завершения функции распознавателя (эта функция будет запущена немедленно).

Пример, который я видел, который помог мне прояснить проблему, состоял в том, что вы можете установить функцию Timeout с помощью reject, например:

new Promise(_, reject) {
 setTimeout(reject, 3000);
});

Выше не могло бы быть возможно написать с броском.

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


2
Это звучит как ключевая концепция, но я не понимаю, как написано. Полагаю, все еще слишком плохо знаком с Обещаниями.
Дэвид Спектор

43

TLDR: функцию трудно использовать, когда она иногда возвращает обещание, а иногда выдает исключение. При написании асинхронной функции предпочитайте сигнализировать об ошибке, возвращая отклоненное обещание

Ваш конкретный пример запутывает некоторые важные различия между ними:

Поскольку вы обрабатываете ошибки внутри цепочки обещаний, сгенерированные исключения автоматически преобразуются в отклоненные обещания. Это может объяснить, почему они кажутся взаимозаменяемыми - это не так.

Рассмотрим ситуацию ниже:

checkCredentials = () => {
    let idToken = localStorage.getItem('some token');
    if ( idToken ) {
      return fetch(`https://someValidateEndpoint`, {
        headers: {
          Authorization: `Bearer ${idToken}`
        }
      })
    } else {
      throw new Error('No Token Found In Local Storage')
    }
  }

Это будет анти-паттерном, потому что вам нужно будет поддерживать как асинхронные, так и синхронизирующие ошибки. Это может выглядеть примерно так:

try {
  function onFulfilled() { ... do the rest of your logic }
  function onRejected() { // handle async failure - like network timeout }
  checkCredentials(x).then(onFulfilled, onRejected);
} catch (e) {
  // Error('No Token Found In Local Storage')
  // handle synchronous failure
} 

Не хорошо, и именно здесь Promise.reject(доступно в глобальном масштабе) приходит на помощь и эффективно дифференцируется от throw. Рефактор теперь становится:

checkCredentials = () => {
  let idToken = localStorage.getItem('some_token');
  if (!idToken) {
    return Promise.reject('No Token Found In Local Storage')
  }
  return fetch(`https://someValidateEndpoint`, {
    headers: {
      Authorization: `Bearer ${idToken}`
    }
  })
}

Теперь это позволяет вам использовать только один catch()для сетевых сбоев и синхронную проверку ошибок на отсутствие токенов:

checkCredentials()
      .catch((error) => if ( error == 'No Token' ) {
      // do no token modal
      } else if ( error === 400 ) {
      // do not authorized modal. etc.
      }

1
Однако пример Op всегда возвращает обещание. Вопрос касается того, следует ли вам использовать Promise.rejectили throwкогда вы хотите вернуть отклоненное обещание (обещание, которое перейдет к следующему .catch()).
Маркос Перейра

@maxwell - ты мне нравишься. В то же время, если в выборке вы добавите поймать и в него выкинете исключение, тогда вы будете в безопасности использовать try ... catch ... В потоке исключений нет идеального мира, но я думаю, что использование одного Отдельный шаблон имеет смысл, и объединение шаблонов небезопасно (в соответствии с вашей моделью против аналогии с шаблонами).
user3053247

1
Отличный ответ, но я нахожу здесь недостаток - этот шаблон предполагает, что все ошибки обрабатываются возвращением Promise.reject - что происходит со всеми непредвиденными ошибками, которые могут просто возникнуть из checkCredentials ()?
Ченоп

1
Да, вы правы @chenop - чтобы поймать эти неожиданные ошибки, вы должны были бы обернуть их в try / catch
maxwell

Я не понимаю дело @ Максвелла. Не могли бы вы просто структурировать это так, чтобы у вас были checkCredentials(x).then(onFulfilled).catch(e) {}, и иметь catchручку как случая отклонения, так и выданного сообщения об ошибке?
Бен Уилер

5

Пример, чтобы попробовать. Просто измените isVersionThrow на false, чтобы использовать reject вместо throw.

const isVersionThrow = true

class TestClass {
  async testFunction () {
    if (isVersionThrow) {
      console.log('Throw version')
      throw new Error('Fail!')
    } else {
      console.log('Reject version')
      return new Promise((resolve, reject) => {
        reject(new Error('Fail!'))
      })
    }
  }
}

const test = async () => {
  const test = new TestClass()
  try {
    var response = await test.testFunction()
    return response 
  } catch (error) {
    console.log('ERROR RETURNED')
    throw error 
  }  
}

test()
.then(result => {
  console.log('result: ' + result)
})
.catch(error => {
  console.log('error: ' + error)
})

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.