JavaScript Promises - отклонение против броска

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

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

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

Использование throw

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

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

Ответ 1

Нет преимущества в использовании одного против другого, но есть особый случай, когда 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
});

Ответ 2

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

Пример:

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

Ответ 3

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

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

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

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

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

Ответ 4

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

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

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

Рассмотрим следующую ситуацию:

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.
      }

Ответ 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)
})