How to level the Pyramid of Death

    To configure webpack according to the manual, program the angular and even send json via ajax - it seems everyone can, but here’s how you look at the code itself ... In this post, the difference between the innovations will be shown.

    So you opened the node and saw that almost all the functions “out of the box” accept the 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]);
            }
        }
    });
    


    Pyramid of death


    And what is the problem actually? The problem is that on a poppy with a retina sometimes the space for spaces ends (of course, you can say that 4 spaces on a tab is a luxury) and the whole 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 take a line of code with them, it will be shown later how to handle this using sugar es6 and es7.

    Promise A

    built-in object that allows you to slightly flatten the pyramid:

    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 little 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 it was thrown by at least one promise in practice, such a change is far from always justified, for example, if you need to check the list of proxies, then you need to check everything , and not break off on the first "dead". This issue is solved by the Q and Bluebird libraries, etc., so we will not cover it.

    Now we rewrite it all 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 very good, but ... after all, there are some other generators that add a new type of function functions * and the yeild keyword, what 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, however this does not mean that the generators are bad, they just poorly fit this task.

    Async / Await

    Two more keywords with a muddy meaning that you can try to stick to a solution that has already bothered with the 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 writing this code is not for example, it would have ended up like this:

    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);
    

    Also popular now: