متخصصین علوم رایانه کشور

Learning MEAN Framework
متخصصین علوم رایانه کشور

آموزش Node.js - قسمت چهارم

يكشنبه, ۲۲ آذر ۱۳۹۴، ۰۴:۵۵ ب.ظ
موضوع:‌ آموزش Callback Pattern - Creating Asynchronous Function
ویرایش:‌ ۱.۱

اجازه دهید مجددا بخشی از سورس‌کدی که در قسمت سوم آموزشی،‌ نوشته‌ایم را بررسی نماییم:

server.listen(3000);
console.log('Web server is listening...');

این کد، کد مناسبی نمی‌باشد! ولی علت چیست؟ واقعیت آن است که دستورات JavaScript چه در سمت Client و چه در سمت Server، به ترتیب اجرا می‌شوند و بسیاری از دستورات پیش‌فرض، مانند دستور listen، به صورت Async اجرا می‌شوند. این بدان معنی است که پس از فرآخوانی دستوری مانند server.listen، سیستم در این خط دچار وقفه نشده و به سرعت سراغ دستور بعدی می‌رود. حال فرض کنید که اجرای دستور listen، به هر دلیلی، پنج ثانیه به طول بیانجامد. در این صورت شما پیام Web server is listening را پنج ثانیه قبل از راه‌اندازی واقعی و در محیط Console خواهید دید! که این اصلا درست نمی‌باشد. از آن بدتر! زمانی است که راه‌اندازی Server به مشکل برخورد کرده و اصلا راه اندازی نشود، در این صورت باز هم شما پیام مذکور را خواهید دید که سرور به درستی راه‌اندازی شده و منتظر درخواست از طرف Client می‌باشد!
با توجه به نکات فوق، به این نتیجه می‌رسیم که باید دستورات فوق را به گونه دیگری بنویسیم. حال به دستورات ذیل توجه نمایید:

server.listen(3000, function(error) {

    if(error != null) {
        console.log('Web server can't listen! - ' + error);
        return;
    }

    console.log('Web server is listening...');
});

کد فوق، یک کد هوشمندانه و مناسب می‌باشد! به محض اینکه سیستم، به دستور server.listen می‌رسد، متوجه می‌شود که باید این دستور را اجرا کند و اقدام به اجرای آن می‌کند. ولی معطل اجرای آن نمی‌شود! و به سرعت، به سراغ دستورات بعد از server.listen خواهد رفت. ولی هرگاه عملیات مربوط به server.listen (چه با خطا و یا بدون خطا) خاتمه یابد، وارد تابع درونی server.listen می‌شود. در صورتی که کار با موفقیت صورت نگرفته باشد، شیء error مخالف null بوده، که در این صورت، پیام خطا در محیط کنسول نمایش داده می‌شود و در صورتی که عملیات با موفقیت صورت گرفته باشد، پیام Web server is listening، در محیط کنسول نمایش داده خواهد شد.
به تابع درونی server.listen، اصطلاحا Callback Function می‌گویند، و به خود تابع listen، یک تابع Asynchronous اطلاق می‌شود و ما به کرات از اینگونه توابع، در هنگام برنامه‌نویسی در محیط‌های Client و یا Server برخورد خواهیم کرد.
ولی چیزی که از مطلب فوق اهمیت بیشتری دارد، آن است که یاد بگیریم، چگونه تفکر و یا الگوی Callback را پیاده‌سازی نماییم، و چگونه اقدام به نوشتن یک توابع Asynchronous نماییم. فرض کنید که ما یک یا چند دستور سنگین داریم که اجرای آنها زمان زیادی را به خود اختصاص می‌دهند. به عنوان مثال فرض کنید، می‌خواهیم یک Data Conversion سنگین انجام دهیم، که به طور تقریبی، ۶۰ ثانیه به طول می‌انجامد. توجه داشته باشید که در محیط‌های Client‌ و یا Server، اجرای دستورات JavaScript، فقط با یک Thread انجام می‌شوند. لذا اگر یکی از کاربران سیستم، باعث شود که اجرای برنامه، به دستورات سنگینی برخورد نماید، متاسفانه تمام کاربران سایت، به مدت ۶۰ ثانیه معطل اجرای دستورات مختص به خود خواهند شد، و این یک فاجعه است! لذا می‌خواهیم الگویی را یاد بگیریم که مناسب اینگونه شرایط است.
برای این منظور، ابتدا دستوراتی که اجرای آنها زمان زیادی را به خود اختصاص می‌دهند را در داخل یک تابع جداگانه‌ای می‌نویسیم. در مثال ذیل، فرض بر این است که تابع مذکور، دارای پارامتر ورودی خاصی نمی‌باشد. در این حالت فقط یک پارامتر ورودی به نام callback، برای تابع مذکور، تعریف می‌کنیم. دقت داشته باشید، در صورتی که بخواهیم برای تابع مذکور، به عنوان مثال، دو پارامتر a و b ارسال نماییم، باید تعداد پارامترهای تابع مذکور را برابر سه پارامتر تعریف نماییم و باید پارامتر آخر آن callback باشد.
پس از نوشتن تابع مذکور، در درون آن از دستور setTimeout استفاده می‌کنیم. دقت داشته باشید که این تابع، کلید Callback Pattern می‌باشد. در شرایط عادی،‌ از این تابع، زمانی استفاده می‌کنیم که بخواهیم دستور یا دستوراتی را پس از گذشت چند میلی ثانیه، اجرا نماییم. ولی در حال حاضر، نیت و هدف دیگری داریم! با استفاده از این تابع، می‌توانیم الگوی Callback را پیاده‌سازی نماییم. لذا در داخل تابع مذکور، تابع setTimeout را نوشته و زمان اجرای آنرا، برابر صفر در نظر می‌گیریم.
function calculateSalary(callback) {
setTimeout(function () {

// Do some process(es) for calculating salary.
for (var index = 1; index <= 1000000000; index++) {
}

callback();
}, 0);
}
در دستورات فوق، فرض بر آن است که دستور for، همان دستوری است که اجرای آن زمان زیادی را به خود اختصاص می‌دهد.
حال زمانی که می‌خواهیم دستورات سنگین و پر هزینه از نظر زمانی مذکور را اجرا نماییم، به صورت ذیل عمل می‌کنیم:

calculateSalary(function () {
console.log('Salary Calculation Done!');
});

حال نمونه دیگری را با هم بررسی می‌کنیم. نمونه‌ای که در آن، تابع مربوطه، به عنوان مثال، دارای دو پارامتر ورودی a‌ و b‌ می‌باشد.

function calculateSalary(a, b, callback) {
setTimeout(function () {

// Do some process(es) for calculating salary.
for (var index = 1; index <= 1000000 * a * b; index++) {
}

callback();
}, 0);
}

calculateSalary(5, 10, function () {
console.log('Salary Calculation Done!');
});


در دو نمونه فوق، دو مشکل اساسی داریم:
۱. در صورتی که اجرای تابع، به مشکلی برخورد کند، راه حل مناسبی پیشنهاد نشده است!
۲. در صورتی که نیاز داشته باشیم که تابع مذکور خروجی در اختیار ما قرار دهد نیز راه حل مناسبی پیشنهاد نشده است!
حال اجازه دهید که بهترین و هوشمندانه‌ترین راه حل را با هم و به کمک مثال ذیل پی می‌گیریم. در مثال ذیل، اولا برای تابع خود، پارامترهای ورودی داریم به نام‌های a و b در نظر گرفته‌ایم، و نیز، در صورت بروز خطا در زمان اجرای تابع مذکور، روش علمی و مناسبی ارایه کرده‌ایم و ثالثا، مقداری را به عنوان نتیجه و یا خروجی تابع ایجاد کرده و برمی‌گردانیم:

function division(a, b, callback) {
setTimeout(function () {

// Do some process(es) for calculating salary.
for (var index = 1; index <= 1000000000; index++) {
}

if (b == 0) {
var error =
new Error('Division by zero!');

callback(error, null);
}
else {
var result = a / b;

callback(null, result);
}
}, 0);
}

division(10, 2, function (error, result) {

if (error != null) {
console.log(error);
}
else {
console.log('Result: ' + result);
}
});

division(5, 0, function (error, result) {

if (error != null) {
console.log(error.message);
}
else {
console.log('Result: ' + result);
}
});

console.log('This message will be shown Immediately!');

  • داریوش تصدیقی

نظرات  (۲)

  • مهدی حسین پور
  • این مثال های آخری یکم پیچیده بود...
    پاسخ:
    مهدی جان، بگو کجاش سخت یا غیر مفهومی بود... بیشتر توضیح بدم دوست من...
  • سینا توکل
  • سلام، امیدوارم خوب و خوش باشید.

    من یک موضوع برام مبهم مونده، طبق مطالب شما
    ما فقط یک Thread داریم
    برای اجرای یک تابع زمان زیادی لازم داریم و برای راه حل آنرا به صورت Callback می نویسیم،
    خطوط بعدی انجام میشود
    اجرای تابع تمام می شود و نتیجه برمی گردد.
    با یک Thread چطوری هم تابع و هم خطوط بعدی به صورت همزمان اجرا می شود ؟؟
    پاسخ:
    سینا جان سلام، این معماری است که موتورهای JavaScript خصوصا موتور V8 گوگل از اون استفاده میکنه... در همون یک Thread، یک Collection از Task ها ذخیره میشه و اون Thread دستوراتی که در داخل این مجموعه قرار داره رو به ترتیب تست میکنه که کدوم یکی از اونها کارش تمام شده و از صف مجموعه خارج می کنه... نمی دونم آیا این یک تعبیر درست باشه یا نه، ولی یکی از دوستان من که معماری OS های Mainframe آگاهی داشت، گفت مثل اونها عمل می کنه...

    ارسال نظر

    ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
    شما میتوانید از این تگهای html استفاده کنید:
    <b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
    تجدید کد امنیتی