Статьи

Решение проблем с обратным вызовом с помощью Async

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

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

Это может быть хорошо для чего-то простого или в ситуации, когда у вас будет запущено несколько экземпляров скрипта, но для многих серверных приложений это кошмар.

Введите асинхронное программирование. В асинхронном скрипте ваш код продолжает выполняться, ожидая чего-то, но может вернуться назад, когда это что-то произошло.

Взять, к примеру, сетевой запрос. Если вы отправляете сетевой запрос медленному серверу, на ответ которого уходит полных три секунды, ваш сценарий может активно выполнять другие действия, пока этот медленный сервер отвечает. В этом случае три секунды могут показаться человеку не слишком большими, но сервер может отвечать на тысячи других запросов во время ожидания. Итак, как вы справляетесь с асинхронностью в 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);
          }
        }
      );
    }
  }
);

Код выглядит довольно неприятно и имеет ряд проблем:

  1. Вы загружаете файлы последовательно; было бы более эффективно, если бы вы могли загружать их оба одновременно и возвращать значения, когда оба полностью загрузились.

  2. Синтаксически это правильно, но трудно читать. Обратите внимание на количество вложенных функций и увеличивающихся вкладок. Вы можете сделать несколько трюков, чтобы он выглядел немного лучше, но вы можете пожертвовать удобочитаемостью другими способами.

  3. Это не очень общая цель. Это прекрасно работает для двух файлов, но что, если у вас было девять файлов иногда, а иногда 22 или только один? То, как оно написано в настоящее время, очень жесткое.

Не волнуйтесь, мы можем решить все эти проблемы (и даже больше) с помощью 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, которая предоставляет несколько метафор, вы можете обнаружить, что написание асинхронного кода доставляет удовольствие.