Настроить webpack по мануалу, запрограммировать ангуляр и даже послать json по ajax — кажись каждый может, но вот как взглянешь на сам код… В этом посте будет показана разница между нововведениями.
Итак вы открыли ноду и увидели, что почти все функции «из коробки» последним аргументом принимают колбэк.
Пирамида смерти
А в чем собственно проблема? Проблема в том, что на маке с ретиной порой заканчивается место под пробелы (конечно можно сказать, что 4 пробела на таб — роскошь) и весь код маячит далеко справа при использовании хотя бы десятка таких функций подряд.

Так что же c этим можно сделать? Не применяя библиотек, для наглядности, так как с ними все примеры не займут и строчки кода, дальше будет показано как с этим справиться используя сахар es6 и es7.
Promise
Встроенный объект позволяющий немного разравнять пирамиду:
Кода стало немного больше, но зато сильно сократилась обработка ошибок.
Обратите внимание .catch был использован два раза потому, что Promise.all использует fail-fast стратегию и бросает ошибку, если ее бросил хотя бы один промис на практике такое пременение далеко не всегда оправдано, например если нужно проверить список проксей, то нужно проверить все, а не обламываться на первой «дохлой». Этот вопрос решают библиотеки Q и Bluebird и тд, поэтому его освещать не будем.
Теперь перепишем это все с учетом arrow functions, desctructive assignment и modules.
Generator
Теперь совсем хорошо, но…ведь есть еще какие-то генераторы, которые добавляют новый тип функций function* и ключевое слово yeild, что будет если использовать их?
Цепочки из generator.next().value.then не лучше чем колбэки из первого примера однако это не значит, что генераторы плохие, они просто слабо подходят под эту задачу.
Async/Await
Еще два ключевых слова, с мутным значением, которые можно попробовать прилепить к решению, уже надоевшей задачи по чтению файлов- Async/Await
Пожалуй самый красивый пример, все функции заняты своим делом и нету никаких пирамид.
Если писать этот код не для примера, то получилось бы как-то так:
Итак вы открыли ноду и увидели, что почти все функции «из коробки» последним аргументом принимают колбэк.
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]);
}
}
});
Пирамида смерти
А в чем собственно проблема? Проблема в том, что на маке с ретиной порой заканчивается место под пробелы (конечно можно сказать, что 4 пробела на таб — роскошь) и весь код маячит далеко справа при использовании хотя бы десятка таких функций подряд.

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);
Так что же c этим можно сделать? Не применяя библиотек, для наглядности, так как с ними все примеры не займут и строчки кода, дальше будет показано как с этим справиться используя сахар es6 и es7.
Promise
Встроенный объект позволяющий немного разравнять пирамиду:
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);
});
Кода стало немного больше, но зато сильно сократилась обработка ошибок.
Обратите внимание .catch был использован два раза потому, что Promise.all использует fail-fast стратегию и бросает ошибку, если ее бросил хотя бы один промис на практике такое пременение далеко не всегда оправдано, например если нужно проверить список проксей, то нужно проверить все, а не обламываться на первой «дохлой». Этот вопрос решают библиотеки Q и Bluebird и тд, поэтому его освещать не будем.
Теперь перепишем это все с учетом arrow functions, desctructive assignment и 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
Теперь совсем хорошо, но…ведь есть еще какие-то генераторы, которые добавляют новый тип функций function* и ключевое слово yeild, что будет если использовать их?
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);
});
});
});
Цепочки из generator.next().value.then не лучше чем колбэки из первого примера однако это не значит, что генераторы плохие, они просто слабо подходят под эту задачу.
Async/Await
Еще два ключевых слова, с мутным значением, которые можно попробовать прилепить к решению, уже надоевшей задачи по чтению файлов- 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);
Пожалуй самый красивый пример, все функции заняты своим делом и нету никаких пирамид.
Если писать этот код не для примера, то получилось бы как-то так:
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);