Когда мы впервые начинаем программировать, мы узнаем, что блок кода выполняется сверху вниз. Это синхронное программирование: каждая операция завершается до начала следующей. Это замечательно, когда вы делаете много вещей, которые компьютер практически не тратит на выполнение, например, добавление чисел, манипулирование строкой или присвоение переменных.
Что происходит, когда вы хотите сделать что-то, что занимает относительно много времени, например, получить доступ к файлу на диске, отправить сетевой запрос или дождаться истечения таймера? В синхронном программировании ваш скрипт больше ничего не может делать, пока он ждет.
Это может быть хорошо для чего-то простого или в ситуации, когда у вас будет запущено несколько экземпляров скрипта, но для многих серверных приложений это кошмар.
Введите асинхронное программирование. В асинхронном скрипте ваш код продолжает выполняться, ожидая чего-то, но может вернуться назад, когда это что-то произошло.
Взять, к примеру, сетевой запрос. Если вы отправляете сетевой запрос медленному серверу, на ответ которого уходит полных три секунды, ваш сценарий может активно выполнять другие действия, пока этот медленный сервер отвечает. В этом случае три секунды могут показаться человеку не слишком большими, но сервер может отвечать на тысячи других запросов во время ожидания. Итак, как вы справляетесь с асинхронностью в Node.js?
Самый простой способ — обратный вызов. Обратный вызов — это просто функция, которая вызывается после завершения асинхронной операции. По соглашению, функции обратного вызова Node.js имеют как минимум один аргумент, err
. Обратные вызовы могут иметь больше аргументов (которые обычно представляют данные, возвращаемые обратному вызову), но первый из них будет err
. Как вы уже догадались, err
содержит объект ошибки (если ошибка была вызвана — подробнее об этом позже).
Давайте посмотрим на очень простой пример. Мы будем использовать встроенный модуль файловой системы Node.js ( fs
). В этом сценарии мы будем читать содержимое текстового файла. Последняя строка файла — это console.log
, в котором задается вопрос: если вы запустите этот скрипт, думаете ли вы, что увидите журнал до того, как увидим содержимое текстового файла?
01
02
03
04
05
06
07
08
09
10
11
12
13
|
var
fs = require(‘fs’);
fs.readFile(
‘a-text-file.txt’, //the filename of a text file that says «Hello!»
‘utf8’, //the encoding of the file, in this case, utf-8
function(err,text) { //the callback
console.log(‘Error:’,err);
console.log(‘Text:’,text);
}
);
//Will this be before or after the Error / Text?
console.log(‘Does this get logged before or after the contents of the text file?’);
|
Поскольку это асинхронно, мы на самом деле увидим последний файл console.log
перед содержимым текстового файла. Если у вас есть файл с именем a-text-file.txt в той же директории, в которой вы выполняете скрипт узла, вы увидите, что err
равен null
, а значение text
заполняется содержимым текстового файла.
Если у вас нет файла с именем a-text-file.txt, err
вернет объект Error, и значение text
будет undefined
. Это приводит к важному аспекту обратных вызовов: вы всегда должны обрабатывать свои ошибки. Чтобы обработать ошибки, вам нужно проверить значение в ошибке переменная; если значение присутствует, то произошла ошибка. Как правило, false
аргументы обычно не возвращают false
, поэтому вы можете проверять только достоверность.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
var
fs = require(‘fs’);
fs.readFile(
‘a-text-file.txt’, //the filename of a text file that says «Hello!»
‘utf8’, //the encoding of the file, in this case, utf-8
function(err,text) { //the callback
if (err) {
console.error(err);
} else {
console.log(‘Text:’,text);
}
}
);
|
Теперь предположим, что вы хотите отобразить содержимое двух файлов в определенном порядке. Вы получите что-то вроде этого:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
var
fs = require(‘fs’);
fs.readFile(
‘a-text-file.txt’, //the filename of a text file that says «Hello!»
‘utf8’, //the encoding of the file, in this case, utf-8
function(err,text) { //the callback
if (err) {
console.error(err);
} else {
console.log(‘First text file:’,text);
fs.readFile(
‘another-text-file.txt’, //the filename of a text file that says «Hello!»
‘utf8’, //the encoding of the file, in this case, utf-8
function(err,text) { //the callback
if (err) {
console.error(err);
} else {
console.log(‘Second text file:’,text);
}
}
);
}
}
);
|
Код выглядит довольно неприятно и имеет ряд проблем:
-
Вы загружаете файлы последовательно; было бы более эффективно, если бы вы могли загружать их оба одновременно и возвращать значения, когда оба полностью загрузились.
-
Синтаксически это правильно, но трудно читать. Обратите внимание на количество вложенных функций и увеличивающихся вкладок. Вы можете сделать несколько трюков, чтобы он выглядел немного лучше, но вы можете пожертвовать удобочитаемостью другими способами.
-
Это не очень общая цель. Это прекрасно работает для двух файлов, но что, если у вас было девять файлов иногда, а иногда 22 или только один? То, как оно написано в настоящее время, очень жесткое.
Не волнуйтесь, мы можем решить все эти проблемы (и даже больше) с помощью async.js .
Обратные вызовы с Async.js
Во-первых, давайте начнем с установки модуля async.js.
1
|
npm install async —-save
|
Async.js можно использовать для склейки массивов функций в последовательном или параллельном режиме. Давайте перепишем наш пример:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
var
async = require(‘async’), //async.js module
fs = require(‘fs’);
async.series( //execute the functions in the first argument one after another
[ //The first argument is an array of functions
function(cb) { //`cb` is shorthand for «callback»
fs.readFile(
‘a-text-file.txt’,
‘utf8’,
cb
);
},
function(cb) {
fs.readFile(
‘another-text-file.txt’,
‘utf8’,
cb
);
}
],
function(err,values) { //The «done» callback that is ran after the functions in the array have completed
if (err) { //If any errors occurred when functions in the array executed, they will be sent as the err.
console.error(err);
} else { //If err is falsy then everything is good
console.log(‘First text file:’,values[0]);
console.log(‘Second text file:’,values[1]);
}
}
);
|
Это работает почти так же, как и в предыдущем примере, последовательно загружая каждый файл, и отличается только тем, что читает каждый файл и не отображает результат до его завершения. Код более лаконичен и чище, чем предыдущий пример (а позже мы сделаем его еще лучше). async.series
принимает массив функций и выполняет их одну за другой.
Каждая функция должна иметь только один аргумент, обратный вызов (или cb
в нашем коде). cb
должен выполняться с аргументами того же типа, что и любой другой обратный вызов, поэтому мы можем поместить его прямо в наши аргументы fs.readFile
.
Наконец, результаты отправляются в последний обратный вызов, второй аргумент в async.series
. Результаты сохраняются в массиве со значениями, соответствующими порядку функций в первом аргументе async.series
.
С async.js обработка ошибок упрощается, потому что, если он обнаруживает ошибку, он возвращает ошибку в аргумент окончательного обратного вызова и не будет выполнять никаких дополнительных асинхронных функций.
Все вместе сейчас
Связанная функция async.parallel
; он имеет те же аргументы, что и async.series
поэтому вы можете переключаться между ними, не меняя остальную часть вашего синтаксиса. Это хороший момент для параллельного и параллельного.
JavaScript в основном однопоточный язык, то есть он может делать только одну вещь за раз. Он способен выполнять некоторые задачи в отдельном потоке (например, большинство функций ввода / вывода), и именно здесь асинхронное программирование вступает в игру с JS. Не путайте параллель с параллелизмом .
Когда вы выполняете две вещи с помощью async.parallel
, вы не заставляете его открывать другой поток для разбора JavaScript или делать две вещи одновременно — вы действительно контролируете, когда он проходит между функциями в первом аргументе async.parallel
. Таким образом, вы ничего не получите, просто поместив синхронный код в async.parallel.
Это лучше всего объяснить визуально:
Вот наш предыдущий пример, написанный для параллельности — единственное отличие состоит в том, что мы используем async.parallel
а не async.series
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
var
async = require(‘async’), //async.js module
fs = require(‘fs’);
async.parallel( //execute the functions in the first argument, but don’t wait for the first function to finish to start the second
[ //The first argument is an array of functions
function(cb) { //`cb` is shorthand for «callback»
fs.readFile(
‘a-text-file.txt’,
‘utf8’,
cb
);
},
function(cb) {
fs.readFile(
‘another-text-file.txt’,
‘utf8’,
cb
);
}
],
function(err,values) { //The «done» callback that is ran after the functions in the array have completed
if (err) { //If any errors occurred when functions in the array executed, they will be sent as the err.
console.error(err);
} else { //If err is falsy then everything is good
console.log(‘First text file:’,values[0]);
console.log(‘Second text file:’,values[1]);
}
}
);
|
Снова и снова
В наших предыдущих примерах было выполнено фиксированное количество операций, но что произойдет, если вам потребуется переменное количество асинхронных операций? Это быстро становится беспорядочным, если вы просто полагаетесь на обратные вызовы и обычные языковые конструкции, полагаетесь на неуклюжие счетчики или проверки условий, которые затеняют реальный смысл вашего кода. Давайте посмотрим на грубый эквивалент цикла for с помощью async.js.
В этом примере мы запишем десять файлов в текущий каталог с последовательными именами файлов и небольшим содержанием. Вы можете изменить число, изменив значение первого аргумента async.times
. В этом примере обратный вызов для fs.writeFile
создает только аргумент err
, но функция async.times
также может поддерживать возвращаемое значение. Как и async.series, он передается обратному вызову done во втором аргументе в виде массива.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
var
async = require(‘async’),
fs = require(‘fs’);
async.times(
10, // number of times to run the function
function(runCount,callback) {
fs.writeFile(
‘file-‘+runCount+’.txt’, //the new file name
‘This is file number ‘+runCount, //the contents of the new file
callback
);
},
function(err) {
if (err) {
console.error(err);
} else {
console.log(‘Wrote files.’);
}
}
);
|
Самое время сказать, что большинство функций async.js по умолчанию работают параллельно, а не последовательно. Итак, в приведенном выше примере он начнет создавать файлы и сообщать, когда все будут полностью созданы и записаны.
Те функции, которые выполняются параллельно по умолчанию, имеют функцию ряда следствий, обозначенную функцией, заканчивающейся, как вы уже догадались, «Series». Поэтому, если вы хотите запустить этот пример последовательно, а не параллельно, вы должны изменить async.times
на async.timesSeries
.
Для нашего следующего примера зацикливания мы рассмотрим функцию async.until. async.until
выполняет асинхронную функцию (последовательно), пока не будет выполнено определенное условие. Эта функция принимает три функции в качестве аргументов.
Первая функция — это тест, в котором вы возвращаете либо true (если вы хотите остановить цикл), либо false (если вы хотите продолжить цикл). Второй аргумент — асинхронная функция, а последний — обратный вызов done. Посмотрите на этот пример:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
var
async = require(‘async’),
fs = require(‘fs’),
startTime = new Date().getTime(), //the unix timestamp in milliseconds
runCount = 0;
async.until(
function () {
//return true if 4 milliseconds have elapsed, otherwise false (and continue running the script)
return new Date().getTime() > (startTime + 5);
},
function(callback) {
runCount += 1;
fs.writeFile(
‘timed-file-‘+runCount+’.txt’, //the new file name
‘This is file number ‘+runCount, //the contents of the new file
callback
);
},
function(err) {
if (err) {
console.error(err);
} else {
console.log(‘Wrote files.’);
}
}
);
|
Этот скрипт создаст новые текстовые файлы за пять миллисекунд. В начале сценария мы получаем время начала в эпоху Unix в миллисекундах, а затем в функции теста мы получаем текущее время и проверяем, не превышает ли оно на пять миллисекунд время начала плюс пять. Если вы запустите этот скрипт несколько раз, вы можете получить разные результаты.
На моей машине я создавал от 6 до 20 файлов за пять миллисекунд. Интересно, что если вы попытаетесь добавить console.log
в тестовую функцию или в асинхронную функцию, вы получите очень разные результаты, потому что для записи в вашу консоль требуется время. Это просто говорит о том, что в программном обеспечении все имеет стоимость производительности!
Для каждого цикла удобная структура — она позволяет вам что-то делать для каждого элемента массива. В async.js это будет функция async.each
. Эта функция принимает три аргумента: коллекцию или массив, асинхронную функцию для выполнения каждого элемента и готовый обратный вызов.
В приведенном ниже примере мы берем массив строк (в данном случае типы борзых собак) и создаем файл для каждой строки. Когда все файлы будут созданы, будет выполнен обратный вызов. Как и следовало ожидать, ошибки обрабатываются с помощью объекта err
в готовом обратном вызове. async.each
запускается параллельно, но если вы хотите запустить его последовательно, вы можете следовать ранее упомянутому шаблону и использовать async.eachSeries
вместо async.each
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
var
async = require(‘async’),
fs = require(‘fs’);
async.each(
//an array of sighthound dog breeds
[‘greyhound’,’saluki’,’borzoi’,’galga’,’podenco’,’whippet’,’lurcher’,’italian-greyhound’],
function(dogBreed, callback) {
fs.writeFile(
dogBreed+’.txt’, //the new file name
‘file for dogs of the breed ‘+dogBreed, //the contents of the new file
callback
);
},
function(err) {
if (err) {
console.error(err);
} else {
console.log(‘Done writing files about dogs.’);
}
}
);
|
Двоюродный брат async.each
— это функция async.map
; разница в том, что вы можете передать значения обратно в готовый обратный вызов. С async.map
функции async.map
вы передаете массив или коллекцию в качестве первого аргумента, а затем асинхронная функция будет выполняться для каждого элемента в массиве или коллекции. Последний аргумент — обратный вызов done.
Пример ниже берет массив пород собак и использует каждый элемент для создания имени файла. Затем имя файла передается в fs.readFile
, где оно читается, а значения передаются обратно функцией обратного вызова. В результате вы получите массив содержимого файла в аргументах готового обратного вызова.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
var
async = require(‘async’),
fs = require(‘fs’);
async.map(
[‘greyhound’,’saluki’,’borzoi’,’galga’,’podenco’,’whippet’,’lurcher’,’italian-greyhound’],
function(dogBreed, callback) {
fs.readFile(
dogBreed+’.txt’, //the new file name
‘utf8’,
callback
);
},
function(err, dogBreedFileContents) {
if (err) {
console.error(err);
} else {
console.log(‘dog breeds’);
console.log(dogBreedFileContents);
}
}
);
|
async.filter
также очень похож по синтаксису на async.each
и async.map
, но с помощью фильтра вы отправляете логическое значение для обратного вызова элемента, а не значение файла. В обратном вызове done вы получаете новый массив, содержащий только те элементы, для которых вы передали true
или истинное значение в обратном вызове элемента.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
var
async = require(‘async’),
fs = require(‘fs’);
async.filter(
[‘greyhound’,’saluki’,’borzoi’,’galga’,’podenco’,’whippet’,’lurcher’,’italian-greyhound’],
function(dogBreed, callback) {
fs.readFile(
dogBreed+’.txt’, //the new file name
‘utf8’,
function(err,fileContents) {
if (err) { callback(err);
callback(
err, //this will be falsy since we checked it above
fileContents.match(/greyhound/gi) //use RegExp to check for the string ‘greyhound’ in the contents of the file
);
}
}
);
},
function(err, dogBreedFileContents) {
if (err) {
console.error(err);
} else {
console.log(‘greyhound breeds:’);
console.log(dogBreedFileContents);
}
}
);
|
В этом примере мы делаем больше вещей, чем в предыдущих. Обратите внимание, как мы добавляем дополнительный вызов функции и обрабатываем собственную ошибку. Шаблон if
err
и callback(err)
очень полезен, если вам нужно манипулировать результатами асинхронной функции, но вы все же хотите, чтобы async.js обрабатывал ошибки.
Кроме того, вы заметите, что мы используем переменную err в качестве первого аргумента функции обратного вызова. На первый взгляд, это выглядит не совсем правильно. Но так как мы уже проверили правдивость ошибки, мы знаем, что ложно и безопасно перейти к обратному вызову.
Через край утеса
До сих пор мы исследовали ряд полезных строительных блоков, которые имеют грубые следствия в синхронном программировании. Давайте async.waterfall
в async.waterfall
, который не имеет аналогов в синхронном мире.
Концепция с водопадом заключается в том, что результаты одной асинхронной функции перетекают в аргументы другой асинхронной функции последовательно. Это очень мощная концепция, особенно при попытке объединить несколько асинхронных функций, которые зависят друг от друга. В async.waterfall
первый аргумент — это массив функций, а второй аргумент — это ваш обратный вызов done.
В вашем массиве функций первая функция всегда будет начинаться с одного аргумента, обратного вызова. Каждая последующая функция должна соответствовать аргументам без ошибок предыдущей функции без функции err и с добавлением нового обратного вызова.
В нашем следующем примере мы начнем объединять некоторые концепции, используя водопад в качестве клея . В массиве, который является первым аргументом, у нас есть три функции: первая загружает список каталогов из текущего каталога, вторая берет список каталогов и использует async.map
для запуска fs.stat
для каждого файла, а третья функция принимает список каталогов из первого результата функции и получает содержимое для каждого файла ( fs.readFile
).
async.waterfall
запускает каждую функцию последовательно, поэтому он всегда запускает все функции fs.stat
перед запуском любого fs.readFile
. В этом первом примере вторая и третья функции не зависят друг от друга, поэтому их можно заключить в async.parallel
чтобы уменьшить общее время выполнения, но мы async.parallel
эту структуру снова для следующего примера.
Примечание. Запустите этот пример в небольшом каталоге текстовых файлов, иначе вы будете долго собирать мусор в окне терминала.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
var
async = require(‘async’),
fs = require(‘fs’);
async.waterfall([
function(callback) {
fs.readdir(‘.’,callback);
},
function(fileNames,callback) { //`fileNames` is the directory listing from the previous function
async.map(
fileNames, //The directory listing is just an array of filenames,
fs.stat, //so we can use async.map to run fs.stat for each filename
function(err,stats) {
if (err) { callback(err);
callback(err,fileNames,stats);
}
}
);
},
function(fileNames,stats,callback) { //the directory listing, `fileNames` is joined by the collection of fs.stat objects in `stats`
async.map(
fileNames,
function(aFileName,readCallback) { //This time we’re taking the filenames with map and passing them along to fs.readFile to get the contents
fs.readFile(aFileName,’utf8′,readCallback);
},
function(err,contents) {
if (err) { callback(err);
callback(err,fileNames,stats,contents);
}
}
);
}
],
function(err, fileNames,stats,contents) {
if (err) {
console.error(err);
} else {
console.log(fileNames);
console.log(stats);
console.log(contents);
}
}
);
|
Допустим, мы хотим получить результаты только для файлов, размер которых превышает 500 байт. Мы могли бы использовать приведенный выше код, но вы получите размер и содержимое каждого файла, независимо от того, нужны они вам или нет. Как вы можете просто получить статистику файлов и только содержимое файлов, которые соответствуют требованиям к размеру?
Во-первых, мы можем вытянуть все анонимные функции в именованные функции. Это личное предпочтение, но оно делает код немного чище и легче для понимания (многоразовое использование для загрузки). Как вы можете себе представить, вам нужно получить размеры, оценить эти размеры и получить только содержимое файлов выше требуемого размера. Это может быть легко достигнуто с помощью чего-то вроде Array.filter
, но это синхронная функция, и async.waterfall ожидает функции асинхронного стиля. Async.js имеет вспомогательную функцию, которая может обернуть синхронные функции в асинхронные, довольно джазово названную async.asyncify
.
Нам нужно сделать три вещи, которые мы async.asyncify
с помощью async.asyncify
. Сначала мы возьмем массивы имени файла и статистики из функции arrayFsStat
и объединим их с помощью map
. Затем мы отфильтруем любые элементы, у которых размер статистики меньше 300. Наконец, мы возьмем объединенный объект имени файла и статистики и снова используем map
чтобы просто получить имя файла.
После того, как мы получим имена файлов размером менее 300, мы будем использовать async.map
и fs.readFile
для получения содержимого. Существует много способов взломать это яйцо, но в нашем случае оно было разбито для максимальной гибкости и повторного использования кода. Это использование async.waterfall
иллюстрирует, как вы можете смешивать и сопоставлять синхронный и асинхронный код.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
var
async = require(‘async’),
fs = require(‘fs’);
//Our anonymous refactored into named functions
function directoryListing(callback) {
fs.readdir(‘.’,callback);
}
function arrayFsStat(fileNames,callback) {
async.map(
fileNames,
fs.stat,
function(err,stats) {
if (err) { callback(err);
callback(err,fileNames,stats);
}
}
);
}
function arrayFsReadFile(fileNames,callback) {
async.map(
fileNames,
function(aFileName,readCallback) {
fs.readFile(aFileName,’utf8′,readCallback);
},
function(err,contents) {
if (err) { callback(err);
callback(err,contents);
}
}
);
}
//These functions are synchronous
function mergeFilenameAndStat(fileNames,stats) {
return stats.map(function(aStatObj,index) {
aStatObj.fileName = fileNames[index];
return aStatObj;
});
}
function above300(combinedFilenamesAndStats) {
return combinedFilenamesAndStats
.filter(function(aStatObj) {
return aStatObj.size >= 300;
});
}
function justFilenames(combinedFilenamesAndStats) {
return combinedFilenamesAndStats
.map(function(aCombinedFileNameAndStatObj) {
return aCombinedFileNameAndStatObj.fileName;
});
}
async.waterfall([
directoryListing,
arrayFsStat,
async.asyncify(mergeFilenameAndStat), //asyncify wraps synchronous functions in a err-first callback
async.asyncify(above300),
async.asyncify(justFilenames),
arrayFsReadFile
],
function(err,contents) {
if (err) {
console.error(err);
} else {
console.log(contents);
}
}
);
|
Сделав еще один шаг вперед, давайте еще больше усовершенствуем нашу функцию. Допустим, мы хотим написать функцию, которая работает точно так же, как указано выше, но с гибкостью, чтобы смотреть в любом направлении. Близкий родственник async.waterfall — async.seq
. В то время как async.waterfall
просто выполняет набор функций, async.seq
возвращает функцию, которая выполняет набор других функций. В дополнение к созданию функции вы можете передать значения, которые войдут в первую асинхронную функцию.
Преобразование в async.seq
занимает всего несколько изменений. Сначала мы изменим directoryListing
для принятия аргумента — это будет путь. Во-вторых, мы добавим переменную для хранения нашей новой функции ( directoryAbove300
). В-третьих, мы возьмем аргумент массива из async.waterfall
и переведем его в аргументы для async.seq
. Наш обратный вызов done для водопада теперь используется в качестве обратного вызова done, когда мы запускаем directoryAbove300
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
var
async = require(‘async’),
fs = require(‘fs’),
directoryAbove300;
function directoryListing(initialPath,callback) { //we can pass a variable into the first function used in async.seq — the resulting function can accept arguments and pass them this first function
fs.readdir(initialPath,callback);
}
function arrayFsStat(fileNames,callback) {
async.map(
fileNames,
fs.stat,
function(err,stats) {
if (err) { callback(err);
callback(err,fileNames,stats);
}
}
);
}
function arrayFsReadFile(fileNames,callback) {
async.map(
fileNames,
function(aFileName,readCallback) {
fs.readFile(aFileName,’utf8′,readCallback);
},
function(err,contents) {
if (err) { callback(err);
callback(err,contents);
}
}
);
}
function mergeFilenameAndStat(fileNames,stats) {
return stats.map(function(aStatObj,index) {
aStatObj.fileName = fileNames[index];
return aStatObj;
});
}
function above300(combinedFilenamesAndStats) {
return combinedFilenamesAndStats
.filter(function(aStatObj) {
return aStatObj.size >= 300;
});
}
function justFilenames(combinedFilenamesAndStats) {
return combinedFilenamesAndStats
.map(function(aCombinedFileNameAndStatObj) {
return aCombinedFileNameAndStatObj.fileName;
})
}
//async.seq will produce a new function that you can use over and over
directoryAbove300 = async.seq(
directoryListing,
arrayFsStat,
async.asyncify(mergeFilenameAndStat),
async.asyncify(above300),
async.asyncify(justFilenames),
arrayFsReadFile
);
directoryAbove300(
‘.’,
function(err, fileNames,stats,contents) {
if (err) {
console.error(err);
} else {
console.log(fileNames);
}
}
);
|
Замечание об обещаниях и асинхронных функциях
Вы можете быть удивлены, почему я не упомянул обещания . Я ничего не имею против них — они довольно удобны и, возможно, более элегантны, чем обратные вызовы, — но они представляют собой другой способ взглянуть на асинхронное кодирование.
Встроенные модули Node.js используют обратные вызовы err
-first, и тысячи других модулей используют этот шаблон. Фактически, именно поэтому в этом руководстве в примерах используется fs
— что-то столь же фундаментальное, как доступ к файловой системе в Node.js, использует обратные вызовы, поэтому спор кодов обратного вызова без обещаний является важной частью программирования Node.js.
Можно использовать что-то наподобие Bluebird, чтобы обернуть обратные вызовы с ошибками в функции , основанные на Promise , но это только поможет вам — Async.js предоставляет множество метафор, которые делают асинхронный код читабельным и управляемым.
Принять асинхронность
JavaScript стал одним из де-факто языков работы в сети. Это не без кривых обучения, и есть множество фреймворков и библиотек, которые также могут вас занять. Если вы ищете дополнительные ресурсы для обучения или использования в своей работе, посмотрите, что у нас есть на рынке Envato .
Но обучение асинхронному — это нечто совершенно другое, и, надеюсь, этот урок покажет вам, насколько он может быть полезен.
Асинхронность — это ключ к написанию серверного JavaScript, но если он не создан должным образом, ваш код может стать неуправляемым звеном обратных вызовов. Используя такую библиотеку, как async.js, которая предоставляет несколько метафор, вы можете обнаружить, что написание асинхронного кода доставляет удовольствие.