# Async map

# Descripción de la Práctica

Para la realización de esta práctica estudie /repase el tema Async Programming in JavaScript.

Un primer objetivo es escribir un programa Node.js que usando fs.readFile

  1. lea en paralelo un conjunto de ficheros pasados como argumentos en línea de comandos y
  2. produzca como salida la concatenación de los mismos en el orden especificado.

No se considera una solución usar fs.readFileSync o timers (setTimeout etc.) o usar promesas. Se pide una solución usando callbacks. Use fs.readFile(path[, options], callback).

Este sería un ejemplo de uso:

$ my-async.mjs -f one.txt -f two.txt -f three.txt -o salida.txt
1

# commander

Con commander (opens new window) es posible indicar una opción que se puede repetir

const program = require('commander');
function collect(value, previous) {
  return previous.concat([value]);
}
program.option('-c, --collect <value>', 'repeatable value', collect, []);
program.parse(process.argv);
console.log(program.collect)
1
2
3
4
5
6
7

Ejecución:

$ node repeatable-option-commander.js -c a -c b -c c
[ 'a', 'b', 'c' ]
1
2

o bien usando puntos suspensivos en la descripción:

import { Command } from 'commander'
const program = new Command()

program.option('-f, --files <values...>', 'Ficheros de entrada')
program.option('-o, --output <value>', 'Fichero de salida', 'test/output.txt')

program.on('--help', () => {
  console.log('')
  console.log('Solves the parallel concat using the async module')
  console.log('Example call:')
  console.log(' $ node concat1.js -f f3.txt -f f2.txt -f f1.txt -o output.txt; cat output.txt')
})

program.parse(process.argv)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# make-big-file.bash

Este script crea un fichero de texto con el nombre, el número de líneas y el contenido especificado:

crguezl ➜ /workspaces/asyncmap-casiano-rodriguez-leon-alu0100291865 (training) $ scripts/make-big-file.bash test/f9 10 chuchu
@crguezl ➜ /workspaces/asyncmap-casiano-rodriguez-leon-alu0100291865 (training) $ tail -n 2 test/f9 
9 chuchu
10 chuchu
1
2
3
4

# create-inputs.bash

Este script crea en el directorio test el número de ficheros f#number.txt especificado con el número de líneas decreciente desde el tamaño especificado hacia abajo:

@crguezl ➜ /workspaces/asyncmap-casiano-rodriguez-leon-alu0100291865 (training) $ scripts/create-inputs.bash 4 1024
@crguezl ➜ /workspaces/asyncmap-casiano-rodriguez-leon-alu0100291865 (training) $ ls -l test/f*.txt
-rw-rw-rw- 1 codespace codespace 6056 Sep 19 12:46 test/f1.txt
-rw-rw-rw- 1 codespace codespace 6042 Sep 19 12:46 test/f2.txt
-rw-rw-rw- 1 codespace codespace 6028 Sep 19 12:46 test/f3.txt
-rw-rw-rw- 1 codespace codespace 6014 Sep 19 12:46 test/f4.txt
1
2
3
4
5
6

# Entrega

# Solución con el Módulo async-js

Lea la sección The Async Module de los apuntes y encuentre una solución usando Async.

Considere la posibilidad de excepciones debidas a que alguno de los ficheros no exista.

Si no se le ocurre una solución, puede consultar las soluciones a la pregunta NodeJS - How to read multiple files asynchronously and write read contents to one file (opens new window) en StackOverflow.

# Solucion sin usar el Módulo async-js

A continuación, busque una solución para este problema sin hacer uso de Async ¿Cómo lo haría? No se considera una solución usar fs.readFileSync o timers (setTimeout etc.) o usar promesas. Se pide una solución usando callbacks.

# Abstracción de la solución

Haciendo abstracción de la solución encontrada en el paso anterior escriba una función asyncMap que funcione como el map del módulo Async y que sirva para cualuier función asíncrona que siga el patrón de callback(err, result):

asyncMap(inputs, (item, cb) => fs.readFile(item, cb), (err, contents) => { ... });
1

# Variante del Problema: Serial en vez de paralelo

Ahora cambiamos el problema para lea en secuencial el conjunto de ficheros pasados como argumentos en línea de comandos y produzca como salida la concatenación de los mismos en el orden especificado. Las mismas restricciones que en el caso anterior.

Provea una función general series que secuencialice cualquier array de funciones asíncronas. Debe funcionar tal como lo hace la función series del módulo Async.js.

Esta sería la forma de uso de la función series:

series(program.files, (file, cb) => fs.readFile(file, "utf-8", cb), function (err, results) {

    if (err == null) {
        var file = fs.createWriteStream(program.output);
        file.on('error', err => { throw new Error("Error en la apertura del archivo " + program.output + " " + err) });
        results.forEach(i => { file.write(i + '\n'); });
        file.end();
    } else {
        throw new Error("Fallo en la lectura de los ficheros\n" + err)
    }
});
1
2
3
4
5
6
7
8
9
10
11

# Files

# Módulos CommonJS y ES6

Esta es la estructura del template de la práctica:

➜  asyncmap-solution git:(main) tree -I node_modules
.
├── README.md
├── concatSerialize.js
├── my-async.mjs
├── package-lock.json
├── package.json
├── scripts
│   ├── create-inputs.bash
│   └── make-big-file.bash
├── sol-using-async.mjs
└── test
    ├── expected.txt
    ├── f1.txt
    ├── f2.txt
    ├── f3.txt
    └── output.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

En concatSerialize.js los módulos son cargados usando require (ver CommonJS (opens new window)) mientras que en sol-using-async.mjs y my-async.mjs se usan los módulos ES6 (ver ECMAScript Modules (opens new window)).

# Variables en el package.json

En el directorio scripts hay dos scripts para la creación de ficheros de prueba y que son usados en la sección scripts del package.json.

➜  asyncmap-solution git:(main) npm run
Lifecycle scripts included in asyncmap-solution@1.0.0:
  test
    npm run clean; npm run create-inputs 3 7; npm run my-async.mjs; cmp --silent test/output.txt test/expected.txt && echo 'OK'

available via `npm run-script`:
  create-inputs
    scripts/create-inputs.bash ${npm_package_config_numfiles} ${npm_package_config_size}
  my-async.mjs
    node my-async.mjs -f test/f*.txt -o test/output.txt
  sol-using-async.mjs
    node sol-using-async.mjs -f test/f*.txt -o test/output.txt
  concatSerialize.js
    node concatSerialize.js -f test/f{1..3}.txt -o test/output.txt
  test-err
    node my-async.js -f f1.txt -f no-existe.txt -f f3.txt -o test/output.txt
  save
    git commit -am save && git push -u origin main
  clean
    rm -f test/f*.txt test/output.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

El package.json ilustra como se pueden definir variables en la sección "config" y usarlas en los scripts de npm referenciándolas con ${npm_package_config_varname}.:

  ...  
  "config": {
    "numfiles": 3,
    "size": 7
  },
  "scripts": {
    ...
    "create-inputs": "scripts/create-inputs.bash ${npm_package_config_numfiles} ${npm_package_config_size}",
    ...
  },

1
2
3
4
5
6
7
8
9
10
11

# Referencias

Grading Rubric#

Comments#

Last Updated: 3 months ago