Ответ
До 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 значительно снизил количество ошибок, связанных с потоком выполнения, и упростил рефакторинг и отладку асинхронного кода.