Во-первых вказано “Создавайте такие хост-функции, которые принимают коллбэки либо всегда синхронно, либо всегда асинхронно” а про dezalgo не сказано. http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony
Ну а во-вторых, автор допустил ошибку в своей “универсальной” функции
readFileAsArray = function(file, cb = () => {})
Сразу бросилось в глаза значение cb по-умолчанию. И не зря. Потому что в случае когда
1) readFileAsArray принимает колбэк и не использует возвращаемый промис и
2) fs.readFile возвращает ошибку
то срабатывает reject(err), не ловится и вызывает unhandledRejection, что в дальнейшем в статье и происходит
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: ENOENT: no such file or directory, open
а автор похоже не понимает почему. Уверен, что значение по-умолчанию надо от cb убрать, а тело должно проверять ее наличие
fs.readFile(file, function(err, data) {
if (err) {
if (!cb) return reject(err);
return cb(err);
}
const lines = data.toString().trim().split('\n');
if (!cb) return resolve(lines);
cb(null, lines);
});
Короткая запись тоже невозможна. По стандарту await возвращает значение если оно не является промисом. await [ 15, 17 ] должен вернуть массив [ 15, 17 ], не заглядывая внутрь него. Заглядывать внутрь массива — значит тратить время на то, что программисту не нужно. Если программисту нужно явно ждать все промисы массива, на это есть Promise.all.
Тогда поясню свою позицию про try-catch.
Так как нас даже warning предупреждает (In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code) что unhandled rejection это фатально, то для эмуляции будущих версий (и для fail fast, без unpredicted state) я выполняю per-process что-то типа
Поэтому у автора два bad practice. Первый — пропускать unhandled rejection, а второй — использовать два await вместо Promise.all. Конечно, если пропускать unhandled rejection, то можно catch если есть await, но чаще всего await просто забыт.
К сожалению, этот код будет работать не на 100% корректно. Представьте, что await1 резолвнется (resolve) через 1000мс, а await2 реджектнется (reject) через 500мс. Пока мы ждем await await1, мы НЕ ждем await await2. Таким образом, await2 отработает как unhandled rejection. И мы не сможем поймать ошибку (которая reject(error) внутри await2) в try-catch выше по стеку.
Promise.all([promise1, promise2]) единственный способ когда нужно ждать несколько промисов одновременно. Так как Promise.all начинает ждать (await) все промисы сразу, то и ошибку можно поймать от любого ожидаемого промиса.
Могу дополнить список: "Mars Will Divide Us" автора Paul Anthony Smith.
Во-первых вказано “Создавайте такие хост-функции, которые принимают коллбэки либо всегда синхронно, либо всегда асинхронно” а про dezalgo не сказано. http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony
Ну а во-вторых, автор допустил ошибку в своей “универсальной” функции
Сразу бросилось в глаза значение cb по-умолчанию. И не зря. Потому что в случае когда
1) readFileAsArray принимает колбэк и не использует возвращаемый промис и
2) fs.readFile возвращает ошибку
то срабатывает reject(err), не ловится и вызывает unhandledRejection, что в дальнейшем в статье и происходит
а автор похоже не понимает почему. Уверен, что значение по-умолчанию надо от cb убрать, а тело должно проверять ее наличие
Работает, но не параллельно, а последовательно. И я боюсь, что, как и у автора, unhandled rejection. Полностью как у автора, только в массиве.
Короткая запись тоже невозможна. По стандарту await возвращает значение если оно не является промисом.
await [ 15, 17 ]
должен вернуть массив[ 15, 17 ]
, не заглядывая внутрь него. Заглядывать внутрь массива — значит тратить время на то, что программисту не нужно. Если программисту нужно явно ждать все промисы массива, на это есть Promise.all.именно это и делает. Promise.all создает общий промис, а await ждет его.
Так как нас даже warning предупреждает (In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code) что unhandled rejection это фатально, то для эмуляции будущих версий (и для fail fast, без unpredicted state) я выполняю per-process что-то типа
process.on('unhandledRejection', (error) => {
console.error('>>> unhandledRejection pid', process.pid);
console.error(error);
process.exit(6);
});
Поэтому у автора два bad practice. Первый — пропускать unhandled rejection, а второй — использовать два await вместо Promise.all. Конечно, если пропускать unhandled rejection, то можно catch если есть await, но чаще всего await просто забыт.
Я говорю о таком коде:
const await1 = new Promise((res) => setTimeout(res, 1000));
const await2 = new Promise((_, rej) => setTimeout(rej, 500));
await await1;
await await2;
await await2;
К сожалению, этот код будет работать не на 100% корректно. Представьте, что await1 резолвнется (resolve) через 1000мс, а await2 реджектнется (reject) через 500мс. Пока мы ждем await await1, мы НЕ ждем await await2. Таким образом, await2 отработает как unhandled rejection. И мы не сможем поймать ошибку (которая reject(error) внутри await2) в try-catch выше по стеку.
Promise.all([promise1, promise2]) единственный способ когда нужно ждать несколько промисов одновременно. Так как Promise.all начинает ждать (await) все промисы сразу, то и ошибку можно поймать от любого ожидаемого промиса.