JavaScript Обіцяє - відкинути проти кидка


384

Я прочитав кілька статей на цю тему, але мені все одно не зрозуміло, чи є різниця між 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
Ах, правда. Тож пояснення до мого коментаря було б, "якби воно було викинуто з асинхронного зворотного дзвінка, який не був обіцяний " . Я знав, що є виняток із цього, я просто не міг згадати, що це було. Я теж вважаю за краще використовувати кидок просто тому, що я вважаю його більш читабельним, і дозволяє мені опустити rejectйого зі свого списку парам.
Кевін Б

Відповіді:


344

Немає переваги використання одного проти іншого, але є конкретний випадок, коли 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це не спрацює, а натомість Promise.reject- кращий вибір. Однак фрагменти не впливають на жоден із цих двох варіантів і дають однаковий результат, незалежно від того, що ви вибрали. Я щось пропускаю?
Аншул

2
так. якщо ви використовуєте кидок у setTimeout, вилов не буде викликаний. ви повинні використовувати те, rejectщо було передано new Promise(fn)зворотній дзвінок.
Кевін Б

2
Дякую @KevinB за те, що залишився поруч. Приклад , наведений О.П. згадує він спеціально хотів порівняти return Promise.reject()і throw. Він не згадує rejectзворотний виклик, наведений у new Promise(function(resolve, reject))конструкції. Тож як ваші два фрагменти справедливо демонструють, коли вам слід використовувати зворотний дзвінок для вирішення, питання ОП не в цьому.
Аншул

200

Ще один важливий факт полягає в тому, що 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"));


50
Ну справа в тому, але порівняння складне. Тому що зазвичай ви повинні повернути свою відхилену обіцянку в письмовій формі return reject(), тому наступний рядок не працюватиме.
AZ.

7
Чому ви хочете його повернути?
lukyer

31
У цьому випадку return reject()це просто скорочення, reject(); returnтобто ви хочете припинити потік. Повернене значення виконавця (функція, передана new Promise) не використовується, тому це безпечно.
Фелікс Сапареллі

47

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

Я бачив приклад, який допомагав з’ясувати проблему для мене, це те, що ви можете встановити функцію Timeout з відхиленням, наприклад:

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

Вищезгадане неможливо було б написати з кидком.

У вашому невеликому прикладі різниця в нерозрізненні, але коли ми маємо справу зі складнішою асинхронною концепцією, різниця між ними може бути різкою.


1
Це звучить як ключова концепція, але я не розумію це як написане. Я все ще занадто нова для обіцянок.
Девід Спектор

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
Приклад Опа завжди повертає обіцянку. Питання стосується того, чи слід використовувати Promise.rejectабо throwколи ви хочете повернути відхилену обіцянку (обіцянку, яка перейде до наступної .catch()).
Маркос Перейра

@maxwell - мені подобається ти, наприклад. У той же час, якщо ви будете додавати вилов, і в ньому ви кинете виняток, тоді ви будете безпечно використовувати спробувати ... catch ... Не існує ідеального світу щодо потоку винятків, але я думаю, що за допомогою одного одинарний візерунок має сенс, а поєднувати шаблони не є безпечним (вирівнюється з вашим шаблоном і проти аналогією візерунка).
користувач3053247

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

1
Так, ти правий @chenop - щоб зловити ті несподівані помилки, які потрібно було б укласти в спробу / зловити все-таки
maxwell

Я не розумію справи @ Максвелла. Не могли б ви просто так структурувати його checkCredentials(x).then(onFulfilled).catch(e) {}і мати catchручку як у випадку відхилення, так і у випадку викинутої помилки?
Бен Вілер

5

Приклад спробувати. Просто змініть 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)
})

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