10 اشتباه رایج با کد همزمان در Node.js
Node.js بر اساس ایده I/O ناهمزمان و غیرمسدودکننده ساخته شده. این باعث میشه برای وب سرورها و APIها خیلی کارآمد باشه. ولی گاهی بچهها توی دام استفاده نادرست از کد همزمان میفتن که منجر به گلوگاههای عملکرد، مشکلات مقیاسپذیری و باگهای غیرمنتظره میشه.
1. قفل کردن Event Loop با کد همزمان
اشتباه:
استفاده از توابع همزمان مثل fs.readFileSync() یا crypto.randomBytesSync() توی یه handler API.
const fs = require('fs');
function handleRequest(req, res) {
const data = fs.readFileSync('./largeFile.txt', 'utf-8');
res.send(data);
}این event loop رو قفل میکنه و از پردازش سایر درخواستها تا وقتی که فایل کاملاً خونده بشه جلوگیری میکنه.
راه حل:
از نسخه async استفاده کن تا event loop آزاد بمونه:
const fs = require('fs/promises');
async function handleRequest(req, res) {
const data = await fs.readFile('./largeFile.txt', 'utf-8');
res.send(data);
}حالا، در حالی که Node.js فایل رو میخونه، سرور میتونه سایر درخواستها رو به جاش که بیهوده منتظر بمونه، پردازش کنه.
2. استفاده از حلقه for به جاش تکرار ناهمزمان
اشتباه:
ترکیب حلقههای همزمان با کد ناهمزمان.
const users = ['Alice', 'Bob', 'Charlie'];
for (let i = 0; i < users.length; i++) {
console.log(await getUserData(users[i])); // ❌ بد
}هر تکرار منتظر تموم شدن قبلی میمونه که حلقه رو کند میکنه.
راه حل:
از Promise.all() استفاده کن تا عملیاتها به صورت موازی اجرا بشن:
await Promise.all(users.map(async (user) => {
console.log(await getUserData(user));
}));همیشه عملیاتهای async رو در صورت امکان موازی کن.
3. قفل کردن Thread اصلی با کار CPU-سنگین
اشتباه:
انجام محاسبات گران قیمت توی event loop:
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
app.get('/compute', (req, res) => {
res.send({ result: fibonacci(40) });
});یه درخواست واحد تمام درخواستهای دیگه رو توی حین محاسبه قفل میکنه.
راه حل:
از worker threads استفاده کن تا محاسبات سنگین رو منتقل کنی:
const { Worker } = require('worker_threads');
app.get('/compute', (req, res) => {
const worker = new Worker('./worker.js', { workerData: 40 });
worker.on('message', (result) => res.send({ result }));
worker.on('error', (err) => res.status(500).send(err));
});حالا، event loop آزاد میمونه و درخواستها گیر نمیکنن.
4. استفاده از JSON.parse() روی Payloadهای بزرگ توی حالت همزمان
اشتباه:
Parsing کردن اشیاء JSON بزرگ به صورت همزمان event loop رو قفل میکنه:
app.post('/upload', (req, res) => {
const data = JSON.parse(req.body); // ❌ قفلکننده
res.send({ success: true });
});راه حل:
از JSON parserهای streaming (بسته stream-json) استفاده کن تا payloadهای بزرگ رو به صورت کارآمد مدیریت کنی:
const { parser } = require('stream-json');
app.post('/upload', (req, res) => {
req.pipe(parser()).on('data', (chunk) => {
console.log(chunk);
});
res.send({ success: true });
});5. استفاده نادرست از await توی حلقهها
اشتباه:
قرار دادن await توی یه حلقه به طور غیرضروری اجرا رو کند میکنه.
for (const id of userIds) {
const user = await getUserById(id); // ❌ کند، یکی یکی اجرا میشه
console.log(user);
}راه حل:
اونا رو به صورت موازی اجرا کن:
const users = await Promise.all(userIds.map(getUserById));
console.log(users);6. تکیه بر require() برای ماژولهای بزرگ توی حلقهها
اشتباه:
فراخوانی require() توی یه تابع به طور مکرر:
function handleRequest(req, res) {
const crypto = require('crypto'); // ❌ هر بار بارگذاری میشه
res.send(crypto.randomBytes(16).toString('hex'));
}راه حل:
require() رو به بیرون منتقل کن تا یک بار بارگذاری بشه:
const crypto = require('crypto');
function handleRequest(req, res) {
res.send(crypto.randomBytes(16).toString('hex'));
}require() همزمانه و ماژولها رو cache میکنه، پس اون رو توی توابع فراخوانی نکن.
7. استفاده از فراخوانیهای همزمان دیتابیس توی یه Endpoint API
اشتباه:
اجرای یه query دیتابیس به صورت همزمان:
const db = require('some-db-lib');
app.get('/user/:id', (req, res) => {
const user = db.getUserSync(req.params.id); // ❌ قفلکننده
res.send(user);
});راه حل:
از نسخههای async استفاده کن:
app.get('/user/:id', async (req, res) => {
const user = await db.getUser(req.params.id);
res.send(user);
});8. خوندن فایلهای بزرگ با fs.readFileSync()
اشتباه:
بارگذاری یه فایل کامل به صورت همزمان:
const data = fs.readFileSync('large-file.txt'); // ❌ قفلکننده
console.log(data);راه حل:
فایل رو stream کن:
fs.createReadStream('large-file.txt').pipe(process.stdout);این از افزایش حافظه جلوگیری میکنه و event loop رو پاسخگو نگه میداره.
9. مدیریت درخواستهای HTTP به صورت همزمان
اشتباه:
ایجاد درخواستهای HTTP قفلکننده:
const request = require('sync-request');
app.get('/data', (req, res) => {
const response = request('GET', 'https://api.example.com/data'); // ❌ قفلکننده
res.send(response.getBody('utf8'));
});راه حل:
از axios یا fetch() برای درخواستهای async استفاده کن:
const axios = require('axios');
app.get('/data', async (req, res) => {
const response = await axios.get('https://api.example.com/data');
res.send(response.data);
});10. استفاده نکردن از process.nextTick() برای Callbackهای فوری
اشتباه:
استفاده از setTimeout(fn, 0) وقتی که یه callback فوری نیازه.
راه حل:
از process.nextTick() استفاده کن:
process.nextTick(() => {
console.log('بلافاصله بعد از عملیات فعلی اجرا شد.');
});نتیجهگیری
کد همزمان توی Node.js ذاتاً بد نیست، ولی استفاده نادرست ازش میتونه اپلیکیشنت رو کند کنه و گلوگاههایی ایجاد کنه. برای دوری از اشتباهات رایج:
→ همیشه از نسخههای async عملیات فایل و دیتابیس استفاده کن.
→ عملیاتهای async رو با
Promise.all()موازی کن.→ کارهای CPU-سنگین رو به worker threads منتقل کن.
→ از streamها برای مدیریت دادههای بزرگ استفاده کن.
→ event loop رو برای عملکرد بهتر آزاد نگه دار.





