📜 ⬆️ ⬇️

How to level the Pyramid of death

Customize the webpack for the manual, program the angular and even send json by ajax - it seems everyone can, but how you look at the code itself ... In this post you will see the difference between the innovations.

So, you opened the node and saw that almost all the out-of-box functions take callback as the last argument.

var fs = require("fs"); fs.readdir(__dirname, function(error, files) { if (error) { console.error(error); } else { for (var i = 0, j = files.length; i < j; i++) { console.log(files[i]); } } }); 

')
Death pyramid


What is the actual problem? The problem is that on a Mac with a retina, the space for spaces sometimes ends (of course, we can say that 4 spaces on a tab is a luxury) and all the code looms far to the right when using at least a dozen such functions in a row.



 var fs = require("fs"); var path = require("path"); var buffers = []; fs.readdir(__dirname, function(error1, files) { if (error1) { console.error(error1); } else { for (var i = 0, j = files.length; i < j; i++) { var file = path.join(__dirname, files[i]); fs.stat(file, function(error2, stats) { if (error2) { console.error(error2); } else if (stats.isFile()) { fs.readFile(file, function(error3, buffer) { if (error3) { console.error(error3); } else { buffers.push(buffer); } }); } }); } } }); console.log(buffers); 


So what can be done with this? Without using libraries, for clarity, since all the examples will not occupy them and the lines of code will continue to be shown how to cope with this using es6 and es7 sugar.

Promise

The built-in object allows to level the pyramid a little:

 var fs = require("fs"); var path = require("path"); function promisify(func, args) { return new Promise(function(resolve, reject) { func.apply(null, [].concat(args, function(error, result) { if (error) { reject(error); } else { resolve(result); } })); }); } promisify(fs.readdir, [__dirname]) .then(function(items) { return Promise.all(items.map(function(item) { var file = path.join(__dirname, item); return promisify(fs.stat, [file]) .then(function(stat) { if (stat.isFile()) { return promisify(fs.readFile, [file]); } else { throw new Error("Not a file!"); } }) .catch(function(error) { console.error(error); }); })); }) .then(function(buffers) { return buffers.filter(function(buffer) { return buffer; }); }) .then(function(buffers) { console.log(buffers); }) .catch(function(error) { console.error(error); }); 


The code has become a bit more, but error handling has been greatly reduced.

Please note .catch was used twice because Promise.all uses a fail-fast strategy and throws an error; if at least one promise has been thrown in practice, such use is not always justified, for example, if you need to check the list of proxies, then you need to check everything and not break off at the first "dead." This issue is decided by the Q and Bluebird libraries, etc., so we will not cover it.

Now we will rewrite all this taking into account arrow functions, desctructive assignment and modules.

 import fs from "fs"; import path from "path"; function promisify(func, args) { return new Promise((resolve, reject) => { func.apply(null, [...args, (err, result) => { if (err) { reject(err); } else { resolve(result); } }]); }); } promisify(fs.readdir, [__dirname]) .then(items => Promise.all(items.map(item => { const file = path.join(__dirname, item); return promisify(fs.stat, [file]) .then(stat => { if (stat.isFile()) { return promisify(fs.readFile, [file]); } else { throw new Error("Not a file!"); } }) .catch(console.error); }))) .then(buffers => buffers.filter(e => e)) .then(console.log) .catch(console.error); 


Generator

Now it’s quite good, but ... there are still some generators that add a new type of function * function and the keyword yeild, what happens if you use them?

 import fs from "fs"; import path from "path"; function promisify(func, args) { return new Promise((resolve, reject) => { func.apply(null, [...args, (err, result) => { if (err) { reject(err); } else { resolve(result); } }]); }); } function getItems() { return promisify(fs.readdir, [__dirname]); } function checkItems(items) { return Promise.all(items.map(file => promisify(fs.stat, [path.join(__dirname, file)]) .then(stat => { if (stat.isFile()) { return file; } else { throw new Error("Not a file!"); } }) .catch(console.error))) .then(files => { return files.filter(file => file); }); } function readFiles(files) { return Promise.all(files.map(file => { return promisify(fs.readFile, [file]); })); } function * main() { return yield readFiles(yield checkItems(yield getItems())); } const generator = main(); generator.next().value.then(items => { return generator.next(items).value.then(files => { return generator.next(files).value.then(buffers => { console.log(buffers); }); }); }); 


The chains from generator.next (). Value.then are no better than the callbacks from the first example, but this does not mean that the generators are bad, they are just poorly suited for this task.

Async / Await

Two more keywords, with muddy meanings, which you can try to stick to the solution, already boring task of reading files- Async / Await
 import fs from "fs"; import path from "path"; function promisify(func, args) { return new Promise((resolve, reject) => { func.apply(null, [...args, (error, result) => { if (error) { reject(error); } else { resolve(result); } }]); }); } function getItems() { return promisify(fs.readdir, [__dirname]); } function checkItems(items) { return Promise.all(items.map(file => promisify(fs.stat, [path.join(__dirname, file)]) .then(stat => { if (stat.isFile()) { return file; } else { throw new Error("Not a file!"); } }) .catch(console.error))) .then(files => { return files.filter(file => file); }); } function readFiles(files) { return Promise.all(files.map(file => { return promisify(fs.readFile, [file]); })); } async function main() { return await readFiles(await checkItems(await getItems())); } main() .then(console.log) .catch(console.error); 


Perhaps the most beautiful example, all functions are busy with their work and there are no pyramids.

If to write this code not for an example, it would turn out somehow so:

 import bluebird from "bluebird"; import fs from "fs"; import path from "path"; const myFs = bluebird.promisifyAll(fs); function getItems(dirname) { return myFs.readdirAsync(dirname) .then(items => items.map(item => path.join(dirname, item))); } function getFulfilledValues(results) { return results .filter(result => result.isFulfilled()) .map(result => result.value()); } function checkItems(items) { return bluebird.settle(items.map(item => myFs.statAsync(item) .then(stat => { if (stat.isFile()) { return [item]; } else if (stat.isDirectory()) { return getItems(item); } }))) .then(getFulfilledValues) .then(result => [].concat(...result)); } function readFiles(files) { return bluebird.settle(files.map(file => myFs.readFileAsync(file))) .then(getFulfilledValues); } async function main(dirname) { return await readFiles(await checkItems(await getItems(dirname))); } main(__dirname) .then(console.log) .catch(console.error); 

Source: https://habr.com/ru/post/304474/


All Articles