از طریق منوی جستجو مطلب مورد نظر خود در وبلاگ را به سرعت پیدا کنید
اجتناب از Callback Hell در Node.js اعتراف میکنم که من یکی از افرادی بودم که تصمیم گرفتم Node.js را یاد بگیرم، صرفاً بهدلیل سر و صدایی که در اطراف آن وجود داشت و اینکه همه درباره آن صحبت میکردند. من فکر کردم که اگر اینقدر زود پشتیبانی می شود باید چیز خاصی در مورد آن وجود داشته باشد روی در آن…
سرفصلهای مطلب
معرفی
اعتراف می کنم که من یکی از آن افرادی بودم که تصمیم گرفتم Node.js را یاد بگیرم، صرفاً به دلیل همهمه های اطراف آن و اینکه همه در مورد آن صحبت می کردند. من فکر کردم که اگر اینقدر زود پشتیبانی می شود باید چیز خاصی در مورد آن وجود داشته باشد روی در زندگی اش من بیشتر از یک پسزمینه C، جاوا و پایتون آمدهام، بنابراین سبک ناهمزمان جاوا اسکریپت با هر چیزی که قبلاً با آن روبرو شده بودم بسیار متفاوت بود.
همانطور که احتمالاً بسیاری از شما می دانید، تمام جاوا اسکریپت واقعاً در زیر یک حلقه رویداد تک رشته ای است که رویدادهای صف را پردازش می کند. اگر قرار بود یک کار طولانی مدت را در یک رشته اجرا کنید، process مسدود می شود و باعث می شود سایر رویدادها باید منتظر بمانند تا پردازش شوند (به عنوان مثال رابط کاربری قطع می شود، داده ها ذخیره نمی شوند و غیره). این دقیقاً همان چیزی است که می خواهید در یک سیستم رویداد محور از آن اجتناب کنید. اینجا یک ویدیوی عالی است که خیلی بیشتر در مورد حلقه رویداد جاوا اسکریپت توضیح می دهد.
برای حل این مشکل مسدود کردن، جاوا اسکریپت به شدت متکی است روی callbacks، که توابعی هستند که پس از یک دوره طولانی اجرا می شوند process (IO، تایمر، و غیره) به پایان رسیده است، بنابراین اجرای کد اجازه می دهد تا پس از انجام وظیفه طولانی مدت ادامه یابد.
downloadFile('example.com/weather.json', function(err, data) {
console.log('Got weather data:', data);
});
مشکل: جهنم برگشت به تماس
در حالی که مفهوم callback در تئوری عالی است، می تواند منجر به کدهای واقعا گیج کننده و خوانا شود. فقط تصور کنید که بعد از تماس مجدد نیاز به برقراری تماس داشته باشید:
getData(function(a){
getMoreData(a, function(b){
getMoreData(b, function(c){
getMoreData(c, function(d){
getMoreData(d, function(e){
...
});
});
});
});
});
همانطور که می بینید، این واقعا می تواند از کنترل خارج شود. مقداری را بریزید if
بیانیه، for
حلقهها، فراخوانیهای تابع یا کامنتها و کدهایی خواهید داشت که خوانا نیستند. مبتدیان به ویژه قربانی این می شوند و نمی دانند چگونه از این “هرم عذاب” اجتناب کنند.
جایگزین، گزینه ها
اطراف آن طراحی کنید
بسیاری از برنامه نویسان تنها به دلیل این (طراحی ضعیف) در جهنم بازگشت به تماس گرفتار می شوند. آنها واقعاً از قبل به ساختار کد خود فکر نمی کنند و تا زمانی که خیلی دیر نشده است متوجه نمی شوند که کد آنها چقدر بد شده است. مانند هر کد دیگری که می نویسید، باید متوقف شوید و به این فکر کنید که چه کاری می توان انجام داد تا قبل یا در حین نوشتن آن ساده تر و خواناتر شود. در اینجا چند نکته وجود دارد که می توانید از آنها استفاده کنید اجتناب از جهنم برگشت تماس (یا حداقل آن را مدیریت کنید).
از ماژول ها استفاده کنید
تقریباً در هر زبان برنامه نویسی، یکی از بهترین راه ها برای کاهش پیچیدگی، ماژولار کردن است. برنامه نویسی جاوا اسکریپت تفاوتی ندارد. هر زمان که در حال نوشتن کد هستید، کمی وقت بگذارید تا به عقب برگردید و بفهمید که آیا الگوی مشترکی وجود داشته است که اغلب با آن مواجه می شوید.
آیا یک کد را چندین بار در مکان های مختلف می نویسید؟ آیا قسمت های مختلف کد شما از یک موضوع مشترک پیروی می کنند؟ اگر چنین است، فرصتی دارید که همه چیز را تمیز کنید و کد را انتزاع کنید و دوباره استفاده کنید.
هزاران ماژول وجود دارد که می توانید برای مرجع به آنها نگاه کنید، اما در اینجا چند مورد وجود دارد که باید در نظر بگیرید. آنها وظایف رایج، اما بسیار خاص را انجام می دهند که در غیر این صورت کد شما را به هم ریخته و خوانایی را کاهش می دهد: متکثر کردن، csv، qs، شبیه.
نام توابع خود را بدهید
هنگام خواندن کد (مخصوصاً کدهای نامرتب و سازماندهی نشده)، به راحتی می توان مسیر جریان منطقی یا حتی نحو را از دست داد، زمانی که فضاهای کوچک با تعداد زیادی تماس تو در تو پر شده است. یکی از راههای کمک به مبارزه با این موضوع، نامگذاری عملکردهای خود است، بنابراین تنها کاری که باید انجام دهید این است که به نام آن نگاهی بیندازید و ایده بهتری در مورد عملکرد آن خواهید داشت. همچنین به چشمان شما یک نقطه مرجع دستوری می دهد.
کد زیر را در نظر بگیرید:
var fs = require('fs');
var myFile = '/tmp/test';
fs.readFile(myFile, 'utf8', function(err, txt) {
if (err) return console.log(err);
txt = txt + '\nAppended something!';
fs.writeFile(myFile, txt, function(err) {
if(err) return console.log(err);
console.log('Appended text!');
});
});
نگاه کردن به این ممکن است چند ثانیه طول بکشد تا متوجه شوید که هر تماس برگشتی چه کاری انجام می دهد و از کجا شروع می شود. افزودن کمی اطلاعات اضافی (نامها) به توابع میتواند تفاوت بزرگی در خوانایی ایجاد کند، بهویژه زمانی که در چندین سطح عمیق در تماسهای مختلف هستید:
var fs = require('fs');
var myFile = '/tmp/test';
fs.readFile(myFile, 'utf8', function appendText(err, txt) {
if (err) return console.log(err);
txt = txt + '\nAppended something!';
fs.writeFile(myFile, txt, function notifyUser(err) {
if(err) return console.log(err);
console.log('Appended text!');
});
});
اکنون فقط یک نگاه سریع به شما می گوید که تابع اول مقداری متن اضافه می کند در حالی که تابع دوم کاربر را از تغییر مطلع می کند.
عملکرد خود را از قبل اعلام کنید
یکی از بهترین راهها برای کاهش شلوغی کد، حفظ تفکیک بهتر کد است. اگر یک تابع callback را از قبل اعلام کرده و بعداً آن را فراخوانی کنید، از ساختارهای عمیق تو در تو که کار کردن با آن را بسیار دشوار می کند، اجتناب خواهید کرد.
پس شما می توانید از این …
var fs = require('fs');
var myFile = '/tmp/test';
fs.readFile(myFile, 'utf8', function(err, txt) {
if (err) return console.log(err);
txt = txt + '\nAppended something!';
fs.writeFile(myFile, txt, function(err) {
if(err) return console.log(err);
console.log('Appended text!');
});
});
…به این:
var fs = require('fs');
function notifyUser(err) {
if(err) return console.log(err);
console.log('Appended text!');
};
function appendText(err, txt) {
if (err) return console.log(err);
txt = txt + '\nAppended something!';
fs.writeFile(myFile, txt, notifyUser);
}
var myFile = '/tmp/test';
fs.readFile(myFile, 'utf8', appendText);
در حالی که این می تواند یک راه عالی برای کمک به کاهش مشکل باشد، اما به طور کامل مشکل را حل نمی کند. هنگام خواندن کدهای نوشته شده به این روش، اگر دقیقاً به خاطر نمی آورید که هر تابع چه کاری انجام می دهد، باید به عقب برگردید و به هر یک نگاه کنید تا جریان منطقی را دوباره دنبال کنید، که ممکن است زمان ببرد.
Async.js
خوشبختانه، کتابخانه ها دوست دارند Async.js وجود داشته باشد تا سعی کند مشکل را مهار کند. Async لایه نازکی از عملکردها را اضافه می کند روی در بالای کد شما قرار دارد، اما می تواند پیچیدگی آن را با اجتناب از تودرتوی برگشتی کاهش دهد.
روشهای کمکی زیادی در Async وجود دارد که میتوانند در موقعیتهای مختلف مانند سلسله، موازی، آبشارو غیره. هر تابع یک مورد استفاده خاص دارد، بنابراین کمی وقت بگذارید تا یاد بگیرید کدام یک در چه موقعیت هایی به شما کمک می کند.
همانطور که Async خوب است، مانند هر چیزی، کامل نیست. با ترکیب سریال، موازی، برای همیشه و غیره بسیار آسان است که به راحتی به جایی که با کدهای نامرتب شروع کرده بودید برمی گردید. مراقب باشید که بهینه سازی زودرس انجام ندهید. فقط به این دلیل که چند کار غیر همگام می توانند به صورت موازی اجرا شوند، همیشه به این معنی نیست که باید انجام شوند. در واقع، از آنجایی که Node فقط تک رشته ای است، وظایف را به صورت موازی اجرا می کند روی استفاده از Async افزایش عملکرد کمی دارد.
کد بالا را می توان با استفاده از آبشار Async ساده کرد:
var fs = require('fs');
var async = require('async');
var myFile = '/tmp/test';
async.waterfall((
function(callback) {
fs.readFile(myFile, 'utf8', callback);
},
function(txt, callback) {
txt = txt + '\nAppended something!';
fs.writeFile(myFile, txt, callback);
}
), function (err, result) {
if(err) return console.log(err);
console.log('Appended text!');
});
وعده ها
اگرچه درک Promises کمی طول می کشد، به نظر من آنها یکی از مهم ترین مفاهیمی هستند که می توانید در جاوا اسکریپت یاد بگیرید. در طول توسعه یکی از من برنامه های SaaS، من در نهایت با استفاده از Promises کل پایگاه کد را بازنویسی کردم. نه تنها تعداد خطوط کد را به شدت کاهش داد، بلکه پیگیری جریان منطقی کد را بسیار آسانتر کرد.
در اینجا یک مثال با استفاده از کتابخانه بسیار سریع و بسیار محبوب Promise آورده شده است. پرنده ابی:
var Promise = require('bluebird');
var fs = require('fs');
Promise.promisifyAll(fs);
var myFile = '/tmp/test';
fs.readFileAsync(myFile, 'utf8').then(function(txt) {
txt = txt + '\nAppended something!';
fs.writeFile(myFile, txt);
}).then(function() {
console.log('Appended text!');
}).catch(function(err) {
console.log(err);
});
توجه کنید که چگونه این راه حل نه تنها کوتاه تر از راه حل های قبلی است، بلکه خواندن آن نیز آسان تر است (اگرچه، مسلماً، کدهای Promise-style ممکن است کمی به آن عادت کنند). برای یادگیری و درک وعده ها وقت بگذارید، ارزش وقت گذاشتن را دارد. با این حال، Promises قطعا راه حلی برای همه مشکلات ما در برنامه نویسی ناهمزمان نیست، بنابراین تصور نکنید با استفاده از آنها یک برنامه سریع، تمیز و بدون اشکال خواهید داشت. نکته کلیدی این است که بدانید چه زمانی برای شما مفید خواهند بود.
چند کتابخانه Promise که باید بررسی کنید، هستند س، پرنده ابی، یا وعده های داخلی اگر از ES6 استفاده می کنید.
Async/Await
توجه: این یک ویژگی ES7 است که در حال حاضر در Node یا io.js پشتیبانی نمیشود. با این حال، شما می توانید آن را در حال حاضر با ترانسپایلری مانند استفاده کنید بابل.
یکی دیگر از گزینههای پاک کردن کد شما، که به زودی مورد علاقه من است (زمانی که پشتیبانی گستردهتری داشته باشد)، استفاده از async
کارکرد. این به شما امکان میدهد کدی بنویسید که بیشتر شبیه کدهای همزمان است، اما همچنان ناهمزمان است.
یک مثال:
async function getUser(id) {
if (id) {
return await db.user.byId(id);
} else {
throw 'Invalid ID!';
}
}
try {
let user = await getUser(123);
} catch(err) {
console.error(err);
}
این db.user.byId(id)
تماس برمی گردد a Promise
، که معمولاً باید با آن استفاده کنیم .then()
، اما با await
می توانیم مقدار حل شده را مستقیماً برگردانیم.
توجه داشته باشید که تابع حاوی await
تماس با پیش شماره است async
، که به ما می گوید که حاوی کد ناهمزمان است و همچنین باید با آن فراخوانی شود await
.
مزیت بزرگ دیگر این روش این است که اکنون می توانیم از آن استفاده کنیم try/catch
، for
، و while
با توابع ناهمزمان ما، که بسیار بصری تر از زنجیر کردن وعده ها با هم است.
جدا از استفاده از ترانسپایلرهایی مانند بابل و ردیاب، همچنین می توانید عملکردهایی مانند این را در Node با استفاده از غیرقابل انتظار بسته بندی
نتیجه
از چنین مشکلات رایجی اجتناب کنید زیرا جهنم بازگشت به تماس آسان نیست، بنابراین انتظار نداشته باشید که فوراً به ناامیدی خود پایان دهید. همه ما گرفتار آن می شویم. فقط سعی کنید سرعت خود را کاهش دهید و کمی زمان بگذارید تا به ساختار کد خود فکر کنید. مثل هر چیز دیگری، تمرین باعث کامل شدن می شود.
آیا وارد جهنم برگشتی شده اید؟ اگر چنین است، چگونه از آن دور می شوید؟ در نظرات به ما بگویید!
(برچسبها برای ترجمه)# روش
منتشر شده در 1403-01-30 16:30:08