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');
});
Во время выполнения этой функции асинхронно бэкэнду приходит другой запрос от другого пользователя — что произойдёт? Обработка запроса начнёт выполняться или всё равно придётся ждать завершения асинхронной функции?Кстати, async вы могли бы и не писать, потому что у вас нет ни одного вызова await.
В общем случае это зависит от того как написан код. Конкретно в случае express — другой запрос начнет выполняться одновременно с первым, да и было бы очень странно если бы это было не так.Вот этот момент ОЧЕНЬ важен! Где об этом можно почитать, удостовериться?
Кстати, async вы могли бы и не писать, потому что у вас нет ни одного вызова await.await должен по идее вызвать Express. Если это не так, то как второй запрос начнёт исполняться одновременно с первым?
await должен по идее вызвать Express
Нет, то что внутри express вас никак не касается. Ключевое слово async не делает с функцией ничего волшебного. Все, что дает async — возможность писать внутри слово await (ну, и еще преобразование результата в обещание). Если вы не пишите внутри функции await — вам не требуется async.
Где об этом можно почитать, удостовериться?
Проще всего — взять да проверить на практике.
И второй момент, важно понимать что весь код внутри колбека тоже синхронный, и существует такая вещь как очередь колбеков т.е пока не выполнится весь код внутри функции следующий колбек не сработает.
Я знаю, что промисы просто блокируют ядро до момента пока эта долгая операция не будет выполнена (как и обычные синхронные функции). Поэтому приходится перекладывать эту задачу на другие ядра процессора, используя воркеров, несмотря на то, что промисы считаюся «асинхронными»
async/await — это и есть сахар для промисов.
Сложные и долгие синхронные операции вовсе не обязательно блокируют ядро, только поток. Ядро процессора вообще нельзя взять и заблокировать, вытесняющая многозадачность не позволит.
Воркеры нужны не для любых операций, а только для ограниченных процессорным временем (CPU-bound), то есть вычислительных.
Все остальные либо исполняются полностью асинхронно, как, например, весь сетевой ввод-вывод, либо выносятся в скрытые потоки пула рантаймом node.js и с точки зрения js тоже являются асинхронными.
Есть в ноде и синхронное API, но оно не рекомендуется к использованию кроме как при инициализации модуля.
Жаль что ни в одной статье о промисах не объясняется про «операции, ограниченные процессорным временем». Создается ложное впечатление, что они позволяют выполнить абсолютно любую операцию на заднем фоне с «низким приоритетом» (с прерываниями), не блокируя при этом интерфейс и другие функции.
Тяжелые вычисления можно выполнять только в отдельных потоках/процессах (если нужно не блокировать основной поток).
Тяжелые вычисления можно выполнять только в отдельных потоках/процессах
Насколько я понимаю долгие синхронные вычисления можно сделать «не блокирующими» добавив в эту функцию прерыватель
setTimeout(50, func)
и сохранять прогресс на каждой итерации. Это бы позволило event loop не ждать завершения этой функции. Конечно, это сложнее чем просто перенести выполнение в другой процесс, но все же это возможноawait Promise.resolve(true);
Это позволит прерывать вычисления без дополнительного кода для сохранения результатов.
true
не нужен, достаточно await Promise.resolve()
.
Но такой подход протолкнет лишь очередь микротасков, те же таймеры останутся ждать окончания вычислений. Чтобы позволить отработать таймерам, придется сделать вот так:
await new Promise(resolve => setTimeout(resolve, 0));
или вот так:
await { then: next => setTimeout(next, 0) };
Зачем ждать чего-то, чего ждать не совсем обязательно?
А разве обычно не строится концепт на ожидании результата? Если речь идёт про последовательные обещания, то решение досточно простое. Всё можно запустить паралельно, нужно запустить паралельно.
В любой технологии можно прострелить себе ногу, если не уметь грамоно ей распоряжаться.
let result = await database.collection.find(query).exec();
res.json({result: result});
Не очень понимаю, как вы можете модифицировать этот код так, чтобы ждать было не нужно?
Если же говорить о случаях, когда совсем-совсем не надо ждать, в голову приходит что-то вроде удаления временного файла после выполнения запроса. Но это делается не то что без await, но и без промисов. Достаточно просто использовать пустой колбэк:
fs.unlink('tempfile.tmp', e => {});
Но это всё же достаточно редкий случай, чаще всего как раз ждать приходится.
Если что, я в JS пришёл после 25+ лет системного программирования и для меня await — по умолчанию сигнал того, что здесь может быть место потери производительности и общая логика при любой возможности должна быть изменена на такую, которая не требует ожидания — неважно, ожидание ли системного семафора или позорное while (something) sleep(WAIT_DELAY);
А можно узнать где именно идёт деградация производительности? В основе Node.js до сих пор используются потоки, реализованные через Observer как и многие другие вещи. Мне кажется вы путаете упрощение с усложнением, так-как читать «реактивный код» на примере с Rx.js сложнее чем последовательные вызовы. Как я уже и писал выше, если программист отдаёт отчёт о том что он делает, такие банальные ошибки не будут допущены.
Руководство по Node.js, часть 7: асинхронное программирование