Pull to refresh

Comments 31

Уважаемые читатели! Пользуетесь ли вы, при программировании для Node.js, конструкцией async/await?
Пользуемся конечно! :)

появилась конструкция async/await. Её использование позволяет писать код, который выглядит как синхронный, но при этом является асинхронным, в частности, не блокирует главный поток.
Вот эта фраза мне не до конца понятна. Главный поток не блокируется, но что это даёт однопоточному Node.js приложению на практике, какие ещё операции могут в это время выполняться?! Допустим у меня какой-нибудь Express.js backend:
var usersRouter = require('./routes/users');

app.use('/users', usersRouter);

вызывает асинхронную функцию ./routes/users.js:
var express = require('express');
var router = express.Router();

router.get('/', async function(req, res, next) {
  res.send('respond with a resource');
});
Во время выполнения этой функции асинхронно бэкэнду приходит другой запрос от другого пользователя — что произойдёт? Обработка запроса начнёт выполняться или всё равно придётся ждать завершения асинхронной функции?
В общем случае это зависит от того как написан код. Конкретно в случае express — другой запрос начнет выполняться одновременно с первым, да и было бы очень странно если бы это было не так.

Кстати, async вы могли бы и не писать, потому что у вас нет ни одного вызова await.
В общем случае это зависит от того как написан код. Конкретно в случае express — другой запрос начнет выполняться одновременно с первым, да и было бы очень странно если бы это было не так.
Вот этот момент ОЧЕНЬ важен! Где об этом можно почитать, удостовериться?

Кстати, async вы могли бы и не писать, потому что у вас нет ни одного вызова await.
await должен по идее вызвать Express. Если это не так, то как второй запрос начнёт исполняться одновременно с первым?
await должен по идее вызвать Express

Нет, то что внутри express вас никак не касается. Ключевое слово async не делает с функцией ничего волшебного. Все, что дает async — возможность писать внутри слово await (ну, и еще преобразование результата в обещание). Если вы не пишите внутри функции await — вам не требуется async.


Где об этом можно почитать, удостовериться?

Проще всего — взять да проверить на практике.

Проще всего — взять да проверить на практике.
Т.е. вы точно не знаете. Зачем было писать тогда?!

Кто-то может ответить на этот вопрос?
Я совершенно точно знаю ответ на вопрос «будут ли запросы выполняться параллельно». Но я не знаю ответа на вопрос «где об этом написано».
Во первых нужно отличать код который будет вызыватся при иницализации модуля и код который будет вызван при событии запроса. Сначала у вас происходит регистрация колбеков а потом уже сервер может принимать запросы.
И второй момент, важно понимать что весь код внутри колбека тоже синхронный, и существует такая вещь как очередь колбеков т.е пока не выполнится весь код внутри функции следующий колбек не сработает.
Кто-нибудь тут знает образом async/await работает при обработке сложных/долгих операций? Также, как и промисы?

Я знаю, что промисы просто блокируют ядро до момента пока эта долгая операция не будет выполнена (как и обычные синхронные функции). Поэтому приходится перекладывать эту задачу на другие ядра процессора, используя воркеров, несмотря на то, что промисы считаюся «асинхронными»
mayorovp, вот вам и практика.
Все смешалось в кучу…

async/await — это и есть сахар для промисов.

Сложные и долгие синхронные операции вовсе не обязательно блокируют ядро, только поток. Ядро процессора вообще нельзя взять и заблокировать, вытесняющая многозадачность не позволит.

Воркеры нужны не для любых операций, а только для ограниченных процессорным временем (CPU-bound), то есть вычислительных.

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

Есть в ноде и синхронное API, но оно не рекомендуется к использованию кроме как при инициализации модуля.
Да, я имел ввиду поток, а не ядро.

Жаль что ни в одной статье о промисах не объясняется про «операции, ограниченные процессорным временем». Создается ложное впечатление, что они позволяют выполнить абсолютно любую операцию на заднем фоне с «низким приоритетом» (с прерываниями), не блокируя при этом интерфейс и другие функции.
UFO just landed and posted this here
такие вопросы отпадают сами собой когда разбираешься что такое event loop, как он работает. Спойлер: асинхронно выполняются только IO операции. Если вы или библиотеки которые вы используете выполняют какие то длинные вычисления то да, event loop на время выполнения таких функций будет их ждать.
Тяжелые вычисления можно выполнять только в отдельных потоках/процессах (если нужно не блокировать основной поток).
Тяжелые вычисления можно выполнять только в отдельных потоках/процессах

Насколько я понимаю долгие синхронные вычисления можно сделать «не блокирующими» добавив в эту функцию прерыватель setTimeout(50, func) и сохранять прогресс на каждой итерации. Это бы позволило event loop не ждать завершения этой функции. Конечно, это сложнее чем просто перенести выполнение в другой процесс, но все же это возможно
Это работает только если таких вычислений не слишком много. Лучше все-таки выносить их в другой поток (через воркеров).
Достаточно эту функцию обозначить как async, а внутри периодически вызывать:
await Promise.resolve(true);
Это позволит прерывать вычисления без дополнительного кода для сохранения результатов.
Хмм, не знал об этом способе, спасибо за информацию!

true не нужен, достаточно await Promise.resolve().


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


await new Promise(resolve => setTimeout(resolve, 0));

или вот так:


await { then: next => setTimeout(next, 0) };
UFO just landed and posted this here
Как перебор с сахаром ведёт к быстрому ожирению, так и чрезмерное использование async/await может привести к замедлению приложения. Мне не раз приходилось наблюдать, когда вместо параллельного вызова нескольких функций, использующих промисы, люди использовали await и совершали вызовы последовательно, хотя этого не требовалось. Поэтому перед тем, как использовать await, всегда стоит крепко подумать, а точно ли там надо чего-то ждать, или можно использовать технику «fire and forget» и не плодить бесполезные ожидания.
Почему бы не обернуть Promise.all и await-нуть полученный промис?
Можно, но зачем? Зачем ждать чего-то, чего ждать не совсем обязательно? По моему опыту, логику исполнения в абсолютном большинстве случаев можно построить так, чтобы избежать применения await в принципе. И это как раз вписывается в архитектуру как Node, так и просто web фронтенда гораздо гармоничнее, чем линейное исполнение кода.
Можно, но зачем? Код с применением async/await становится более лаконичный для чтения, так же его проще дебажить, так как там нет бесконечных цепочек .then() в stack trace. И что вы понимаете под
Зачем ждать чего-то, чего ждать не совсем обязательно?

А разве обычно не строится концепт на ожидании результата? Если речь идёт про последовательные обещания, то решение досточно простое. Всё можно запустить паралельно, нужно запустить паралельно.

В любой технологии можно прострелить себе ногу, если не уметь грамоно ей распоряжаться.
Я строю концепты на реактивщине в основном и там, если результат получен, оповещаются наблюдатели, а не крутится что-то где-то в ожидании чего-то. Как-то удаётся обойтись без await в принципе. Поэтому всё работает бодро и без затыков.
Погодите, вы утверждаете, что ждать вообще не нужно? Я что-то не очень понимаю. Если вы делаете запрос к БД, например, то ждать вам придется, иначе вы не сможете ответить клиенту. Вот и выходит что-то вроде:
let result = await database.collection.find(query).exec();
res.json({result: result});

Не очень понимаю, как вы можете модифицировать этот код так, чтобы ждать было не нужно?

Если же говорить о случаях, когда совсем-совсем не надо ждать, в голову приходит что-то вроде удаления временного файла после выполнения запроса. Но это делается не то что без await, но и без промисов. Достаточно просто использовать пустой колбэк:
fs.unlink('tempfile.tmp', e => {});

Но это всё же достаточно редкий случай, чаще всего как раз ждать приходится.
Как раз код с ожиданием результата из базы с последующей отправкой в res.json() просто заворачивается в промис. Да, коллбэк. Для того, чтобы спокойно относиться к коллбэкам, просто не нужно создавать из них ад :)
Если что, я в JS пришёл после 25+ лет системного программирования и для меня await — по умолчанию сигнал того, что здесь может быть место потери производительности и общая логика при любой возможности должна быть изменена на такую, которая не требует ожидания — неважно, ожидание ли системного семафора или позорное while (something) sleep(WAIT_DELAY);
Ну и при чем тут семафоры и активное ожидание, если за await скрывается тот же самый вызов then у промиса?
А речь и не шла изначально о том, что конкретно await в Node чем-то отличается от then колбэка. Речь идёт о построении логики работы приложения — стремление всё уложить в «синхронный» поток с помощью await чревато деградацией производительности из-за того, что увлёкшись этой самой линейностью, люда часто забывают о порядка запуска параллельных задач. Отказ от использования await в данном случае просто побуждает искать более выверенные пути data & event flow. Но да, определённой ценой читабельности кода.

А можно узнать где именно идёт деградация производительности? В основе Node.js до сих пор используются потоки, реализованные через Observer как и многие другие вещи. Мне кажется вы путаете упрощение с усложнением, так-как читать «реактивный код» на примере с Rx.js сложнее чем последовательные вызовы. Как я уже и писал выше, если программист отдаёт отчёт о том что он делает, такие банальные ошибки не будут допущены.

Выше уже упоминали использование Promise.all, о котором вспоминают только отдающие себе отчёт программисты, которых, по моим наблюдениям, мало. Rx.js мне пока не продали (может, просто мало пытались пока), мой собственный «реактивный код» читать не так сложно, я надеюсь — там простецкие on/off/once/emit и троттлинг по месту, вводить какие-то ещё сущности я не вижу необходимости, чтобы в конце концов не получилось как в том анекдоте «а сейчас мы со всей этой фигнёй попробуем взлететь».
Sign up to leave a comment.