Ответ
До ES2017 и async/await асинхронный код в Node.js писали с помощью колбэков и промисов, что часто приводило к сложностям в поддержке.
1. Эра колбэков (Callback Hell / Pyramid of Doom): Основной паттерн в ранних версиях Node.js. Глубоко вложенные колбэки быстро становились нечитаемыми.
const fs = require('fs');
function processFiles(callback) {
fs.readFile('data.json', 'utf8', (err, config) => {
if (err) return callback(err);
const configObj = JSON.parse(config);
fs.readFile(configObj.inputFile, 'utf8', (err, data) => {
if (err) return callback(err);
const processed = data.toUpperCase();
fs.writeFile(configObj.outputFile, processed, (err) => {
if (err) return callback(err);
fs.readFile('log.txt', 'utf8', (err, log) => {
if (err) return callback(err);
const newLog = log + 'nProcessing complete';
fs.writeFile('log.txt', newLog, (err) => {
callback(err, 'Done');
});
});
});
});
});
}
2. Библиотеки для управления колбэками:
Чтобы справиться с этим, широко использовалась библиотека async.js.
const async = require('async');
async.waterfall([
(callback) => fs.readFile('data.json', 'utf8', callback),
(config, callback) => {
const configObj = JSON.parse(config);
fs.readFile(configObj.inputFile, 'utf8', callback);
},
(data, callback) => {
const processed = data.toUpperCase();
fs.writeFile('output.txt', processed, (err) => callback(err, processed));
}
], (err, result) => {
if (err) console.error('Error:', err);
else console.log('Result:', result);
});
3. Приход промисов (Promises):
С появлением нативных промисов (ES6) код стал структурированнее, но оставались длинные цепочки .then() и .catch().
const { readFile, writeFile } = require('fs').promises; // Используем fs.promises
function processWithPromises() {
return readFile('data.json', 'utf8')
.then(config => {
const configObj = JSON.parse(config);
return readFile(configObj.inputFile, 'utf8');
})
.then(data => {
const processed = data.toUpperCase();
return writeFile('output.txt', processed).then(() => processed);
})
.then(result => {
console.log('Success:', result);
return result;
})
.catch(err => {
console.error('Failed:', err);
throw err;
});
}
Почему async/await стал революцией?
Он позволил писать асинхронный код в почти синхронном стиле, сохраняя всю мощь промисов, но радикально повышая читаемость и упрощая обработку ошибок через try/catch.
// Тот же функционал с async/await
async function processWithAsyncAwait() {
try {
const config = await readFile('data.json', 'utf8');
const configObj = JSON.parse(config);
const data = await readFile(configObj.inputFile, 'utf8');
const processed = data.toUpperCase();
await writeFile('output.txt', processed);
console.log('Success:', processed);
return processed;
} catch (err) {
console.error('Failed:', err);
throw err;
}
}
Переход на async/await значительно снизил количество ошибок, связанных с потоком выполнения, и упростил рефакторинг и отладку асинхронного кода.
Ответ 18+ 🔞
Э, представляешь, как мы раньше жили-то? До того как async/await придумали, асинхронный код в Node.js писался на колбэках и промисах. И это была такая пиздопроебибна, что волосы дыбом вставали, честное слово.
1. Эра колбэков (Callback Hell / Пирамида Судьбы): Это, блядь, основной паттерн был в раннем Node.js. Вложенные колбэки так глубоко уходили, что читать это было невозможно, ёпта. Прямо пирамида какая-то проклятая.
const fs = require('fs');
function processFiles(callback) {
fs.readFile('data.json', 'utf8', (err, config) => {
if (err) return callback(err);
const configObj = JSON.parse(config);
fs.readFile(configObj.inputFile, 'utf8', (err, data) => {
if (err) return callback(err);
const processed = data.toUpperCase();
fs.writeFile(configObj.outputFile, processed, (err) => {
if (err) return callback(err);
fs.readFile('log.txt', 'utf8', (err, log) => {
if (err) return callback(err);
const newLog = log + 'nProcessing complete';
fs.writeFile('log.txt', newLog, (err) => {
callback(err, 'Done');
});
});
});
});
});
}
Смотришь на это — и удивление пиздец. Чистая хитрая жопа, где потеряться проще простого. Одна ошибка — и всё, приехали.
2. Библиотеки-костыли:
Чтобы как-то выжить, народ хватался за библиотеку async.js. Это как попытка навести порядок в бардаке с помощью ещё одного бардака, но покрасивше.
const async = require('async');
async.waterfall([
(callback) => fs.readFile('data.json', 'utf8', callback),
(config, callback) => {
const configObj = JSON.parse(config);
fs.readFile(configObj.inputFile, 'utf8', callback);
},
(data, callback) => {
const processed = data.toUpperCase();
fs.writeFile('output.txt', processed, (err) => callback(err, processed));
}
], (err, result) => {
if (err) console.error('Error:', err);
else console.log('Result:', result);
});
Лучше, конечно, но всё равно чувствуешь себя каким-то полупидором, который прыгает через обручи. Доверия к этому коду — ебать ноль.
3. Эпоха промисов:
Потом пришли нативные промисы из ES6. Ну, вроде прогресс, структура появилась. Но эти бесконечные цепочки .then() и .catch()... Бля буду, опять писать овердохуища текста.
const { readFile, writeFile } = require('fs').promises; // Используем fs.promises
function processWithPromises() {
return readFile('data.json', 'utf8')
.then(config => {
const configObj = JSON.parse(config);
return readFile(configObj.inputFile, 'utf8');
})
.then(data => {
const processed = data.toUpperCase();
return writeFile('output.txt', processed).then(() => processed);
})
.then(result => {
console.log('Success:', result);
return result;
})
.catch(err => {
console.error('Failed:', err);
throw err;
});
}
Чувак, смотри: вроде логика есть, но глаза разбегаются. Каждый раз думаешь: "А этот then к чему относится? А ошибка где поймается?" Терпения ноль, ебать.
А потом случилась магия — async/await.
И это, блядь, реально стало революцией. Всё просто: пишешь код почти как синхронный, но вся асинхронная мощь остаётся. И ошибки ловить через try/catch — красота же!
// Тот же функционал с async/await
async function processWithAsyncAwait() {
try {
const config = await readFile('data.json', 'utf8');
const configObj = JSON.parse(config);
const data = await readFile(configObj.inputFile, 'utf8');
const processed = data.toUpperCase();
await writeFile('output.txt', processed);
console.log('Success:', processed);
return processed;
} catch (err) {
console.error('Failed:', err);
throw err;
}
}
Читаешь сверху вниз — и всё понятно, как божий день. Никаких пирамид, никаких цепочек. Просто ждём результат и идём дальше. Переход на эту штуку сэкономил столько нервов и времени, что не передать. Количество ошибок, связанных с "а что когда выполнится", упало в разы. Рефакторить и дебажить стало в кайф, а не как раньше — с молитвой и крепким словцом.