آموزش 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...');
});
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);
}
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!');
});
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!');
});
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!');
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!');
- ۹۴/۰۹/۲۲