Асинхронность в Node.js¶
Асинхронность представляет возможность одновременно выполнять сразу несколько задач. Асинхронность играет большую роль в Node.js.
Например, допустим в файле приложения app.js у нас расположен следующий код:
function displaySync(data) console.log(data); > console.log('Начало работы программы'); displaySync('Обработка данных. '); console.log('Завершение работы программы');
Это стандартный синхронный код, все вызовы здесь выполняются последовательно, что мы можем увидеть, если мы запустим приложение:
Для рассмотрения асинхронности изменим код файла app.js следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
function display(data, callback) // с помощью случайного числа определяем ошибку var randInt = Math.random() * (10 - 1) + 1; var err = randInt > 5 ? new Error( 'Ошибка выполнения. randInt больше 5' ) : null; setTimeout(function () callback(err, data); >, 0); > console.log('Начало работы программы'); display('Обработка данных. ', function (err, data) if (err) throw err; console.log(data); >); console.log('Завершение работы программы');
В начале также определяется функция display , но теперь кроме данных в качестве второго параметра она принимает функцию обратного вызова, которая и обрабатывает данные.
Эта функция callback принимает два параметра — информацию об ошибке и собственно данные. Это общая модель функций обратного вызова, которые передаются в асинхронные методы — первым идет параметр, представляющий ошибку, а второй — данные.
Для имитации ошибки используется случайное число: если оно больше 5 , то создаем объект ошибки — объект Error , иначе же он равен null .
И последний важный момент — выполнение функции обратного вызова в функции setTimeout() . Это глобальная функция, которая принимает в качестве первого параметра функцию обратного вызова, а в качестве второго — промежуток, через который функция обратного вызова будет выполняться. Для нашей задачи вполне подойдет промежуток в 0 миллисекунд.
При вызове функции display в нее передается функция, которая в случае отсутствия ошибок просто выводит данные на консоль:
display('Обработка данных. ', function (err, data) if (err) throw err; console.log(data); >);
Теперь если мы запустим приложение, то увидим, следующую картину:
Несмотря на то, что в setTimeout передается промежуток 0 , фактическое выполнение функции display завершается после всех остальных функций, которые определены в программе. В итоге выполнение на функции display не блокируется, а идет дальше. И это особенно актуально, если в приложении идет какая-либо функция ввода-вывода, например, чтения файла или взаимодействия с базой данных, выполнение которой может занять продолжительное время. То общее выполнение приложение не блокируется, а идет дальше.
Почему так происходит? Потому что все колбеки или функции обратного вызова в асинхронных функциях (в качестве таковой здесь используется функция setTimeout ) помещаются в специальную очередь, и начинают выполняться после того, как все остальные синхронные вызовы в приложении завершат свою работу. Собственно поэтому выполнение колбека из функции setTimeout в примере выше происходит после выполнения вызова console.log(«Завершение работы программы»); . И стоит подчеркнуть, что в очередь колбеков переходит не функция, которая передается в display , а функция, которая передается в setTimeout .
Рассмотрим пример с двумя асинхронными вызовами:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
function displaySync(callback) callback(); > console.log('Начало работы программы'); setTimeout(function () console.log('timeout 500'); >, 500); setTimeout(function () console.log('timeout 100'); >, 100); displaySync(function () console.log('without timeout'); >); console.log('Завершение работы программы');
Несмотря на то, что в функцию display передается колбек, но эта функция с колбеком будет выполняться синхронно.
А колбеки из функций setTimeout будут выполняться только после всех остальных вызовов приложения.
Node.JS — Основы асинхронного программирования, часть 1
Сейчас, после выхода стабильной версии Node.JS 0.2.0, я решил начать цикл статей по программированию с его использованием.
Основная концепция Node.JS — любые операции ввода-вывода по умолчанию реализованы как асинхронные, после выполнения операции будет вызвана функция обратного вызова, первым параметром которой будет являться ошибка или null.
Скрываем асинхронную вложенность
Предположим, нам нужно создать каталог, включая всех его родителей. И только в случае, если его удалось создать, начать писать в этот каталог.
Для того, чтобы скрыть сложность работы с асинхронными операциями, вынесем работу по созданию каталога в отдельный асинхронный метод:
var path = require( ‘path’ );
var fs = require( ‘fs’ );
var mkdir_p = function (pth, callback)
fs.stat(pth, function (err, stat)
if (!err && stat)
callback( null );
return ;
>
mkdir_p(path.dirname(pth), function (err)
if (err)
callback(err);
return ;
>
fs.mkdir(pth, 0755, callback);
>);
>);
>;
* This source code was highlighted with Source Code Highlighter .
Функция сперва проверяет наличие каталога, и в случае его наличия вызывает функцию обратного вызова без ошибки. В случае, если каталог отсутствует, вызывается mkdir_p для каталога-родителя, и затем создаётся искомый каталог.
Как видите, удалось удачно реализовать рекурсию, и стандартным для Node.JS передать информацию об ошибках в функцию обратного вызова.
mkdir_p(path.dirname(outPath), function (err)
if (err)
response.writeHead(500, < 'Content-Type' : 'text/plain' >);
response.end( ‘Cannot create directory’ );
return ;
>
getFile(url, inPath, function (err)
if (err)
response.writeHead(500, < 'Content-Type' : 'text/plain' >);
response.end( ‘Cannot read file’ );
return ;
>
transcode(inPath, outPath, function (err)
if (err)
response.writeHead(500, < 'Content-Type' : 'text/plain' >);
response.end( ‘Cannot transcode file’ );
return ;
>
sendRemoveFile(outPath, response, function (err)
if (err)
console.log( ‘Cannot send file’ );
return ;
>
>);
>);
>);
>);* This source code was highlighted with Source Code Highlighter .
В следующей статье расскажу вам о работе с внешними процессами.