Нормально ли бросать (throw) внутри async функции?
Где-то читал, что нехорошо кидаться ошибками изнутри async функций 
... надо только возвращать rejected Promise.
В этом `где-то` вас обманули, так как любое возвращаемое из `async` функции значение (да, включая throw) уже обёрнуто в `Promise`. 

const foo = async () => { throw new Error('Smth went wrong'); };
const bar = async () => Promise.reject(new Error('Smth went wrong'));
Работают абсолютно одинаково
throw бросит исключение, которое ты можешь обработать когда-то там потом, и гарантирует, что все дальнейшее исполнение будет прервано.

reject не бросает исключения и исполнение кода не прервется. Исключение будет выброшено когда-то там потом, когда промис этого захочет.
new Promise((resolve, reject) => {
  reject("reject msg");
  console.log("wtf?????");
})