Callback Hell در nodejs

در این مقاله می خواهیم درباره مقوله جهنم callbackها صحبت کنیم.شاید تا این لحظه این مسئله را بدانید که جاوااسکریپت رویدادها را در یک حلقه رویداد و بر روی یک thread اجرا می کند و این باعث می شود که اگر شما یک فرآیند سنگین را به اجرا درآورید به دلیل اینکه این فرآیند تنها بر روی یک thread اجرا می شود در لحظه ممکن است اجرای فرآیند متوقف شود و شاید یکی از دلایل آن این باشد که سایر فرآیندها در انتظار اجرای آن فرآیند می مانند در نتیجه برنامه برای مدت زمانی متوقف می شود.
جاوااسکریپت هم برای برطرف کردن این مشکل از callbackها استفاده می کند یعنی استفاده از توابعی که بعد از اجرای یک تابع به صورت خودکار اجرا می شوند تا اجرای فرآیند در نهایت به اتمام برسد. اکر به کد زیر دقت داشته باشید مشاهده می کنید که چگونه callback برای اجرای فرآیند مورد استفاده قرار می گیرد.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// First Function with a callback function One(callback) { setTimeout(function () { console.log("This is function one"); callback(); }, 1000); } // Second Function function Two() { console.log("This is function two"); } One(function(){ Two(); }); |
ولی وقتی از callbackها برای اجرای فرآیندها استفاده می کنیم با مشکل اساسی برخورد می کنیم شاید اصطلاحا نام آن را بتوانیم callback hell یا به عبارتی جهنم callbackها بگذاریم پس بسیار شگفت انگیز است که وقتی از callbackها استفاده می کنیم فرآیند اجرای برنامه ها را به Asynchronous تغییر می دهیم ولی این سناریو را در نظر داشته باشید که وقتی از callbackها پش سر هم استفاده می کنید چه اتفاقی رخ می دهد.شک نکنید که در قدم اول خوانایی کد شما را به حداقل می رسند و درنهایت باعث افزایش سردرگمی شما می شود. به نمونه کد زیر دقت کنید :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
function One(a, callback) { callback(a); } function Two(b, callback) { callback(b); } One("a", function (b) { Two(b, function (c) { Two(c, function (d) { Two(d, function (e) { // And so on }); }); }); }); |
دقیق مشاهده می کنید که وقتی چندین تابع را در قالب callbackها فراخوانی می کنید چه اتفاقی می افتد شاید در نگاه اول به راحتی بتوانید کد فوق را تحلیل کنید و متوجه آن شوید ولی تصور کنید که از چندین if و یا for استفاده کنید! شک نکنید که در آن مرحله دیگر هیج کنترلی بر روی اجرای فرآیند خود ندارید. این همان چیزی است که اکثر برنامه نویسان نوپا در این حوزه دچار این مشکل می شوند. در هر حال برای هر مشکلی راهکاری نیز وجود دارد که در ادامه به راهکارهایی برای حل این مشکل اشاره می کنیم.
1. ساختار کد را تغییر دهید :
بسیاری از برنامه نویسان با callback hell آشنا هستند و تنها نحوه کد نویسی آن ها باعث به وجود آمدن همچین مشکلی می شود همچنین معمولا به کدی که نوشتند توجهی نمی کنند و همین مسئله باعث می شود که متوجه تاثیر کد در روند اجرای فرآیند و زمان اجرا نشوند بنابراین همیشه باید به این مقوله توجه داشته باشید که چگونه برنامه های خود را پیاده سازی کنید همیشه باید به این فکر باشید که چگونه جهنم callbackها را دور بزنید هرچند که راهکارهایی برای این کار وجود دارد و در ادامه آن ها را مورد بررسی قرار می دهمیم.
2. کدنویسی ماژولار :
تقریبا در هر زبان برنامه نویسی این مقوله از اهمیت زیادی برخوردار است و یکی از بهترین روش ها برای کاهش پیچیدگی و بهم ریختگی برنامه می باشد و جاوااسکریپت هم از این قضیه مثتثنا نیست.
هر زمان که کد می نویسید، مدتی وقت بگذارید تا بفهمید الگوی مشترکی وجود دارد که اغلب از آن استفاده می کنید و سپس سعی کنید برای آن ماژول درست کنید. با این کار نه تنها می توانید پیچیدگی کد را به حداقل برسانید بلکه می توانید قابلیت استفاده مجدد از کد را نیز افزایش دهید
3. نامگذاری مناسب :
وقتی یک برنامه را مورد بررسی قرار می دهید مخصوصا برنامه هایی که از نظر کد نویسی به شدت به هم ریخته هستند بی شک در جایی از کد دچار سردرگمی می شوید مخصوصا قسمتی که از چندین callback استفاده کرده اید و حتی این مسئله تا حدی برایتان مشکل ساز خواهد شد که منطق برنامه را هم حتی متوجه نمی شوید.
یکی از راهکارهای اساسی برای جلوگیری از چنین مشکلی استفاده از نام های منطقی برای توابع می باشد به طوری که وقتی به کد مراجعه می کنید به راحتی متوجه کاری که هر function می کند شوید.
4. مدیریت خطا :
Errorهای مختلفی وجود دارد که به هنگام توسعه یک برنامه با آن مواجه می شوید ولی باید به درستی هر یک از آن ها را مورد بررسی قرار دهید و برطرف نمایید خطاهایی که به دلیل کانفیگ اشتباه در برنامه رخ می دهند و معمولا زمانی که برنامه را نخستین بار اجرا می کنید نشان داده می شوند و یا خطاهایی که به دلیل مشکلات کدنویسی به وجود می آیند. در نهایت با توجه به اطلاعاتی که از هر خطای به وجود آمده در اختیار دارید باید آن ها را برطرف نمایید.
سه مورد اول مربوط به خوانایی کد شما می شود و مورد آخر مربوط به پایداری کد. شما برای برنامه خود یک استراتژی را مشخص می کنید و درنهایت با توجه به آن کدهای برنامه خود را می نویسید و در کنار آن ممکن است از callbackها استفاده کنید تا یک فرآیند را در پس زمینه برنامه خود اجرا نمایید و هیچ شخصی نمی تواند با قاطعیت بگوید که در مسیر اجرای برنامه تان خطایی رخ می دهد یا خیر بنابراین باید آمادگی برخورد با همچین خطاهایی را داشته باشید. به کد زیر دقت کنید :
1 2 3 4 5 6 7 8 |
var fs = require('fs') fs.readFile('File/Does/not/exist', handleFile) function handleFile(error, file) { if (error) return console.error('There was an error', error) // otherwise, continue on and use `file` in your code } |
همانطور که میبینید error را بعنوان پامتر اول تابع در نظر گرفتیم و پارامتر دوم در واقع خروجی دلخواه است اگر از پارامتر Error استفاده نکنیم باز هم فرآیند اجرا می شود ولی اگر خطایی رخ دهد به درستی نمی توانیم این error را مدیریت و برطرف نمایید.
همانطور که تاکنون متوجه شده اید استفاده نادرست از callbackها باعث بروز مشکلاتی می شود و می تواند روند اجرای برنامه را در حالی که یک ویژگی برای برنامه محسوب می شود مختل نماید به همین منظور راهکارهایی برای جایگزینی callbackها وجود دارد که در ادامه به آن اشاره می کنیم.
1. Async :
ماژول async یک ساختار قدرتمند برای پیاده سازی استراتژی Asynchronous در جاواسکریپت است و یکی از راه حل های ساخت یافته برای اجتناب از callback hell می باشد. هر چند که برای استفاده در nodejs طراحی شده است با استفاده از دستور npm I async قابل نصب است و البته به صورت مستقیم در browser مورد استفاده قرار می گیرد.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// for use with Node-style callbacks... var async = require("async"); var obj = {dev: "https://d2ffcs5wrcif.cdn.shift8web.com/dev.json", test: "https://d2ffcs5wrcif.cdn.shift8web.com/test.json", prod: "https://d2ffcs5wrcif.cdn.shift8web.com/prod.json"}; var configs = {}; async.forEachOf(obj, (value, key, callback) => { fs.readFile(__dirname + value, "utf8", (err, data) => { if (err) return callback(err); try { configs[key] = JSON.parse(data); } catch (e) { return callback(e); } callback(); }); }, err => { if (err) console.error(err.message); // configs is now a map of JSON data doSomethingWith(configs); }); |
2. Async/await :
روش تکامل یافته مورد بالا که در نهایت باعث افزایش خوانایی کد می شود وقتی async را به ابتدای یک function اضافه می کنید ماهیت آن تغییر می کند. Async function یک مقدار را در قالب promise برگشت می دهد.به همین جهت برای دسترسی به این مقدار نیاز است از .()then و یا await استفاده کنید.
1 2 3 4 5 6 7 |
async function f() { return 1; } f().then(alert); // 1 |
1 |
async function f() { return Promise.resolve(1);} f().then(alert); // 1 |
3. Promise :
یکی دیگر از راه حل ها برای اجتناب از callback hell ها استفاده از promise می باشد.promise در جاوااسکریپت روشی است برای مدیریت asyncها . قبل از معرفی promise در ES6، async برای مدیریت callback function ها مورد استفاده قرار می گرفت.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
var express = require('express'); var app = express(); function One() { return new Promise(function (resolve, reject) { let thisIsOne = true; if(thisIsOne) { resolve("You are in function One()."); } else { reject("You are not in function One().") } }) } app.get('/', function(req, res, next) { var promise = One(); promise.then(function onResolved(data) { res.send(data); }, function nReject(data) { res.send(data); }); }); module.exports = app; |
مشکل promise این است که باید زمان زیادی را صرف یادگیری و درک فرآیندهای آن کنید اما ارزش آن را دارد ولی این را بدانید که promiseها راه حل تمام مشکلات برنامه نویسی ناهمزمان شما نیست و از تمام راهکارهایی که گفته شد می توانیم برای حل این مشکل استفاده کنیم.
دیدگاهتان را بنویسید