JavaScript обещает-отклонить или бросить


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

5 187

5 ответов:

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

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!'
});

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

пример:

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

vs

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"));

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

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

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

выше не может быть возможно написать с throw.

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

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

пример для опробования. Просто изменить isVersionThrow false, чтобы использовать отклонить, вместо того, чтобы бросить.

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