تاریخ انتشار: پنجشنبه, ۱ خرداد ۱۳۹۹، ۱۲:۵۸ ق.ظ نویسنده: محمدیان

Nest.js یک چارچوب Node.js سمت سرور برای ساختن برنامه های کارآمد ، قابل اعتماد و مقیاس پذیر است و مورد حمایت بسیاری از سازمانها و افراد معتبر قرار گرفته است.

Nest.js برای حل مشکل معماری Node.js با ارائه برنامه های پشتیبان یک ساختار مدولار برای سازماندهی کد در ماژول های جداگانه معرفی کرده است.

کاملاً با TypeScript ساخته شده است ، دارای مزایای بررسی نوع کد و تزریق وابستگی است که به تسهیل روند توسعه برنامه ها کمک می کند. اگر با ساختار برنامه های Angular آشنا هستید ، می توانید با مفاهیم کلیدی Nest.js احساس راحتی کنید و شروع کار با آن کار ساده ای خواهد بود. به هر حال ، این پست جزئیات لازم را برای شروع ساختن برنامه های کاربردی با Nest.js. برای شما فراهم می کند.

علاوه بر این ، لیست زیر برخی از مزایای Nest.js را توضیح داده است :

  • با استفاده از بلوک های try , catch کنترل کننده مسیر شما را احاطه کرده میکند.
  • هر کدام از هندلرهای مسیریاب به صورت async کار میکنند.
  • مسیریاب بر پایه فریمورک اکسپرس در کل برنامه فعالیت میکند.
  • یک مسیریاب جدا از هم برای هر کنترلر ایجاد می کند
  • واسطه یا میان افزار (middleware) مدیریت خطا را برای کل برنامه ایجاد میکند و زحمت ایجاد آن بر دوش شما نیست.
  • واسطه یا میان افزار body-parser که حتما با آن آشنا هستید، در کل برنامه قابل استفاده است. (هم json و هم urlencoded)

اکنون که به مزایای این چارچوب جالب توجه پی بردید. در پست های بعدی به ترتیب راجع به اجزای ساختاری Nestjs پرداخته خواهد شد.

 

 

 

 

تاریخ انتشار: پنجشنبه, ۱ خرداد ۱۳۹۹، ۱۲:۲۷ ق.ظ نویسنده: محمدیان

نست فریمورکی برای ساختن اپلیکشن های بک اند در NodeJs هست که با TypeScript ساخته شده و بطور کامل ازش پشتیبانی میکنه و همچنین قابلیت های کار با وب سوکت، GraphQl ،Rest و ... رو برامون فراهم میکنه.

در لایه های زیرین خودش از فریمورک ExpressJs بطور پیشفرض استفاده میکنه و همچنین میتونید بجای اکسپرس از Fastify هم استفاده کنید. در واقع یک لایه ابسترکشن روی فریمورکهای express یا fastify هست و در عین حال امکان این رو بهمون میده که مستقیما با api های این فریمورک ها هم کار کنیم و خوبیش اینه که میتونیم از لایبری ها و ماژول هایی که برای این فریمورک ها هست به راحتی استفاده کنیم.

چرا بجای اکسپرس از نست استفاده کنیم؟

یکی از دلایل میشه گفت وجود نداشتن ساختار و استانداردی در پروژه های nodejs هست. هر موقع که وارد یک پروژه جدید نود میشیم احتمال زیاد با یک ساختار متفاوت رو به رو میشیم مثلا بعضی ها ممکنه فقط از یک فایل استفاده کرده باشند ! یا بعضیا از معماری MVC و خیلی چیزای دیگه. این حالت بیشتر واسه کسایی که تازه کار هستن و زیاد با معماری ها و ساختارها آشنا نیستن میتونه گیج کننده باشه و در کل احتمال تولید کد کثیف و غیرقابل نگهداری با گذشت زمان بیشتر میشه که در نهایت کار توسعه رو سخت تر و زمانبر میکنه.

نست فقط یک ساختار و معماری رو بهمون معرفی میکنه و میگه که فقط توی این قالب و استایل کد بزنین.

کامپوننت ها معماری یک اپلیکشن نست به این صورته:

  • کنترلر ها (Controllers)
  • ماژول ها (Modules)
  • پروایدر Provider ها (services, repositories, factories, helpers , ...)
  • کلاس های DTO
  • کلاس های مدل (Entities)

دلیل اصلی استفاده از این معماری و ساختار این هست که اپلیکشن رو به بخش های کوچیکی بشکنیم. یادگیری و کارکردن به این شیوه برای کسایی که انگولار یا spring boot کار کردن میتونه ساده باشه.

در ادامه به معرفی این کامپوننت ها بصورت اجمالی میپردازیم.


کنترلر ها (Controllers):

هدف اصلی دریافت درخواست از کاربر یا هرچیزی دیگه ای هست و فرستادنش به کامپوننت سرویس ها (business logic) برای انجام کار مورد نظر و سپس برگرداندن پاسخ.

ماژول ها (Modules):

ماژول مثل یک کانتینر هست که بقیه بخش هایی که بهم مرتبط هستن رو در خودش نگه میداره .

مثلا یک ماژول بنام user فقط شامل سرویس ها و کنترل ها و ... مخصوص و مرتبط کار با یوزر هاست و به این صورت بخش های مختلف اپ رو جداسازی میکنه.

پروایدر ها Providers:

این بخش مقداری عمومی هستش و میتونه شامل ریپازیتوری مدل های دیتابیس، سرویس، کلاس های helper و ... باشه. ایده کلی پرواید ها اینه که میتونه در جاهای مختلف به صورت وابستگی تزریق بشه، مثل یک کانکشن دیتابیس که میتونه در بخش ها و کلاس های مختلفی تزریق بشه و امکان کار کردن با دیتابیس رو بده. در واقع سرویس ها شامل business logic و منطق برنامه هستن که میتونن در هرجایی استفاده بشن.

کلاس های DTO:

این بخش که مخفف Data Transfer Object هست یک ساختار و فرمت برای داده هایی که در یک اندپوینت (route) از کاربر دریافت میشه تعیین میکنه. مثلا میخواید که کاربر سن رو بصورت عدد براتون بفرسته یا نام بصورت یک رشته باشه. که با ترکیب اینها با لایبری هایی مثل class-validator میتونیم بعنوان ولیدشن هایی قوی ازشون استفاده کنیم و داده های ورودی کاربر رو کنترل کنیم.

مدل ها (Entities):

مدل ها جایی هستن که اطلاعات مربوط به دیتابیس رو توی اون ها میسازیم مثلا با استفاده از typeorm که یک ORM کاملا سازگار با تایپ اسکریپت هست میتونیم یک کلاس مدل بسازیم و جدول دیتابیس رو بدون یک خط کد SQL و فقط با کد تایپ اسکریپت درست کنیم.

سخن پایانی:

این فریمورک استاداردها و استایلی رو جلوی پامون میزاره که باعث افزایش کارایی کدنویسی میشه و ممکنه که در نگاه اول سخت بنظر بیاد ولی وقتی که یادش گرفتید درکنار تایپ اسکریپت میتونه به سرعت کدنویسی، خوانایی، ساختار منظم، نگهداری کد و ... کمک زیادی کنه.

میتونید به سایت nestjs مراجعه کنید که داکیومنتش خیلی خوب توضیح داده و به راحتی میتونید از اونجا یاد بگیرید.

تاریخ انتشار: پنجشنبه, ۱ خرداد ۱۳۹۹، ۱۲:۱۸ ق.ظ نویسنده: محمدیان

برای دسترسی به هر صفحه وب باید یک وب سرور داشته باشید. یک سرور قادر است تا درخواست های HTTP را دریافت کرده و با کمک کد ها شما، پاسخ را به کاربر ارسال کند.

به عنوان مثال در زبان asp.net وب سرور IIS مورد استفاده قرار می گیرد و برای اجرای کد های جاوا یا PHP وب سرورهایی مانند apache وجود دارد.

نود.جی اس به شما این امکان را می دهد تا وب سرور خودتان را با چند خط کد ساده ایجاد کنید و درخواست های HTTP را به صورت ناهمگام (asynchronous) پاسخ دهید.

شما می توانید از وب سرورهای IIS و آپاچی نیز برای اجرای کدهای Node.js استفاده کنید گرچه این کار پیشنهاد نمی شود.

ساخت یک وب سرور با استفاده از Node.js

نود.جی اس به سادگی قادر است تا یک وب سرور را برای مدیریت درخواست های ورودی راه اندازی کند.

مثال زیر نشان می‌دهد که شما چگونه می توانید یک وب سرور را در فایلی به نام server.js به کمک Node.js بسازید و اجرا کنید:

var http = require('http'); // 1 - Import Node.js core module

var server = http.createServer(function (req, res) {   // 2 - creating server

    //handle incomming requests here..

});

server.listen(5000); //3 - listen for any incoming requests

console.log('Node.js web server at port 5000 is running..')

فصل ۱۰ – مثال ۱ (فایل server.js)

در مثال بالا ما ماژول http را به کمک فانکشن require() در فایل ایمپورت کرده ایم.

ماژول http یک ماژول هسته ای برای Node.js است پس نیازی نیست که آن را به کمک این NPM نصب کنید.

قدم بعدی این است که متود createServer() تعبیه شده درون http را با پارامتر های req و res صدا بزنیم، این دو متغیر حاوی اطلاعات درخواست (request) و پاسخ (response) خواهند بود. مقدار بازگردانده شده را به متغیر server انتساب خواهیم داد، زیرا همچنان با این سرور ایجاد شده کار داریم.

در نهایت ما باید متود listen() از متعلقات متود createServer() را صدا بزنیم. این کار با کد server.lister(port) انجام می شود. port در اینجا عدد پورتی که به اپلیکیشن می خواهید اختصاص دهید خواهد بود.

پورت اصلی سرور ها ۸۰ است و نیاز به تایپ کردن ندارد، اگر پورت چیزی غیر از ۸۰ باشد باید با دو نقطه پس از آدرس در مرورگر وارد شود. مثلا localhost:8080 باید وارد شود ولی localhost:80 را می شود به صورت localhost استفاده کرد.

کد server.listen(5000) به نود.جی اس می گوید که به پورت ۵۰۰۰ سرور توجه کند. مثلا هر درخواستی که به آدرس localhost:5000 ارسال شد را دریافت کند.

در مثال بالا ما همچنان به سیستم نگفته ایم که در صورتی که درخواست را دریافت کرد چه پاسخی به آن بدهد.

برای اجرای کد های بالا فقط کافی است در محیط ترمینال یا CMD کد node server.js را تایپ کنید.

پس از اجرای کد پاسخ Node.js web server at port 5000 is running.. از سیستم دریافت خواهد شد.

حالا که ساختن یک وب سرور را آموختید بگذارید به شما نشان دهیم که چگونه می توانید پاسخ درخواست ها را نیز ارسال کنید. اگر پاسخی به درخواست ها ارسال نکنید کاربر در صورت ارسال درخواست در مرورگر صفحه ای مشاهده نخواهد کرد.

مدیریت درخواست های HTTP در Node.js

در متود http.createServer() دو پارامتر response و request تعبیه شده است که برای مدیریت پاسخ های HTTP مورد استفاده قرار می گیرند.

آبجکت request حاوی اطلاعاتی در مورد درخواست ارسال شده به سرور است. اطلاعاتی مانند آدرس درخواستی، header های ارسالی با درخواست و data در این آبجکت وجود دارد.

ابجکت response را می توان برای ارسال پاسخ متناسب به درخواست استفاده کرد.

مثال زیر نشان می‌دهد که شما چگونه می توانید یک درخواست HTTP را با کمک Node.js مدیریت کنید:

var http = require('http'); // Import Node.js core module

var server = http.createServer(function (req, res) {   //create web server
    if (req.url == '/') { //check the URL of the current request
        
        // set response header
        res.writeHead(200, { 'Content-Type': 'text/html' }); 
        
        // set response content    
        res.write('This is home Page.');
        res.end();
    
    }
    else if (req.url == "/student") {
        
        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.write('This is student Page.');
        res.end();
    
    }
    else if (req.url == "/admin") {
        
        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.write('This is admin Page.');
        res.end();
    
    }
    else
        res.end('Invalid Request!');

});

server.listen(5000); //6 - listen for any incoming requests

console.log('Node.js web server at port 5000 is running..')

فصل ۱۰ – مثال ۲ (فایل server.js)

در مثال بالا req.url استفاده شده است تا اپلیکیشن آدرس درخواستی کاربر را تشخیص دهد.

برای ارسال پاسخ متناسب با درخواست کاربر ابتدا ما باید Header مناسب را با متود writeHead() به پاسخ اضافه کنیم و سپس با استفاده از متود write() متنی را بر روی صفحه چاپ کنیم.

در نهایت در هنگامی که Node.js با متود end() برخورد می کند اطلاعات را نشان خواهد داد.

پس از اجرای کد پاسخ Node.js web server at port 5000 is running.. از سیستم دریافت خواهد شد.

اکنون می توانید در مرورگر خود آدرس http://localhost:5000 را باز کرده و متن This is home page را مشاهده کنید

اگر شما آدرس http://localhost:5000/student را در مروگر باز کنید نتیجه زیر را مشاهده خواهید کرد:

در حال حاضر هر درخواست دیگری به سرور نتیجه ” Invalid Request” خواهد داشت.

ارسال پاسخ JSON به درخواست های HTTP در Node.js

مثال زیر نشان می دهد چگونه می توانید پاسخ JSON به درخواست های HTTP ارسال کنید:

var http = require('http'); 

var server = http.createServer(function (req, res) {   
   
    if (req.url == '/data') { //check the URL of the current request
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.write(JSON.stringify({ message: "Hello World"}));  
            res.end();  
    }
});

server.listen(5000);

console.log('Node.js web server at port 5000 is running..')

فصل ۱۰ – مثال ۳ (فایل server.js)

اگر بعد از اجرای مثال بالا، آدرس http://localhost:5000/data را در مرورگر باز کنید، با جمله Hello World در قالب JSON مواجه خواهید شد.

 

 

تاریخ انتشار: پنجشنبه, ۱ خرداد ۱۳۹۹، ۱۲:۱۱ ق.ظ نویسنده: محمدیان

خواندن فایل ها با استفاده از ماژول fs در Node.js

شما می توانید با استفاده از متود fs.readFile() یک فایل را به صورت ناهمگام بخوانید.

سینتکس زیر نحوه تعریف یک متد خواندن فایل را با استفاده از ماژول fs در Node.js نشان می دهد:

fs.readFile(fileName [,options], callback)

توضیح پارامترهای سینتکس بالا:

filename: آدرس کامل یا همان full path فایلی که قرار است خوانده شود. (در قالب رشته)

options: این پارامتر می‌تواند یک رشته و یا Object باشد که حاوی encoding و flag است. encoding پیشفرض برابر utf8 و flag برابر r است. (بعدا بیشتر توضیح می دهیم)

callback: یک فانکشن است که دو پارامتر err و fd را دریافت می کند. پارامتر err حاوی پیغام های خطا و پارامتر fd حاوی اطلاعات درون فایل خواهد بود.

مثال زیر نشان می دهد که چگونه می توانیم اطلاعات فایلی به نام TestFile.txt که در کنار اسکریپت ما قرار دارد را به صورت ناهمگام بخوانیم:

var fs = require('fs');

fs.readFile('TestFile.txt', function (err, data) {
                    if (err) throw err;

    console.log(data);
});

فصل ۱۱ – مثال ۱

هنگامی که از ناهمگام بودن نحوه خواندن اطلاعات از فایل ها صحبت می کنیم منظورمان این است که ابتدا نرم افزار شروع به خواندن اطلاعات فایلها می کند و پس از اتمام فعالیت خود فانکشن callback را صدا می زند.

در مثال بالا پارامتر err در صورتی که فایل موجود نباشد حاوی مقداری خواهد بود. در غیر این صورت پارامتر data اطلاعات فایل را دارا می باشد.

به عنوان مثال در کنار فایل js مثال بالا، فایلی ایجاد کنید به نام TextFile.txt که حاوی متن Hello word باشد.

سپس در محیط ترمینال یا CMD با زدن دستور node server.js، فایل اصلی را اجرا کنید.

در صورتی که مشکلی وجود نداشته باشد باید متن Hello word را در محیط کنسول مشاهده کنید.

همچنین می توانید با استفاده از متود fs.readFileSync() به صورت همگام اطلاعات را بخوانید.

مثال زیر نشان می دهد که چگونه می توانید با استفاده از متود fs.readFileSync() یک فایل را به صورت همگام یا synchronous بخوانید:

var fs = require('fs');

var data = fs.readFileSync('TestFile.txt', 'utf8');
console.log(data);

فصل ۱۱ – مثال ۲

نوشتن بر روی فایل با استفاده از ماژول fs در Node.js

نوشتن بر روی یک فایل به همان سادگی خواندن فایل ها در Node.js است. با استفاده از متود fs.writeFile() قادر هستید تا بر روی یک فایلی که در حال حاضر موجود است اطلاعاتی را بنویسید.

سینتکس نگارش متود fs.writeFile() به شرح زیر است:

fs.writeFile(filename, data[, options], callback)

توضیح پارامترهای سینتکس بالا:

filename: آدرس کامل یا همان full path فایلی که قرار است بر روی آن متنی شود. (در قالب رشته)

data: اطلاعاتی که باید درون فایل نوشته شود.

options: این پارامتر می‌تواند یک رشته و یا Object باشد که حاوی encoding و flag است. encoding پیشفرض برابر utf8 و flag برابر r است. (بعدا بیشتر توضیح می دهیم)

callback: یک فانکشن است که دو پارامتر err و fd را دریافت می کند. پارامتر err حاوی پیغام های خطا و پارامتر fd حاوی اطلاعات درون فایل خواهد بود.

مثال زیر یک فایل به نام test.txt ایجاد خواهد کرد و به صورت ناهمگام متن “Hello World” را درون آن خواهد نوشت:

var fs = require('fs');

fs.writeFile('test.txt', 'Hello World!', function (err) { 
                        if (err)
        console.log(err);
                        else
        console.log('Write operation complete.');
});

فصل ۱۱ – مثال ۳

توجه داشته باشید که مثال بالا، اگر فایل موجود باشد، هر چیزی که درون این فایل از قبل موجود باشد را پاک خواهد کرد و سپس متن جدید را جایگزین آن خواهد کرد.

اگر می خواهید که اطلاعات درون یک فایل پاک نشود و اطلاعات جدید بعد از اطلاعات حال حاضر درون این فایل قرار بگیرد می توانید از متود fs.appendFile() استفاده کنید.

مثال زیر نشان می دهد که چگونه می توانید جمله “Hello Word!” را بعد از هر چیزی که درون فایل test.txt وجود دارد بنویسید:

var fs = require('fs');

fs.appendFile('test.txt', 'Hello World!', function (err) { 
                        if (err)
        console.log(err);
                        else
        console.log('Append operation complete.');
});

فصل ۱۱ – مثال ۴

باز کردن یک فایل با ماژول fs در Node.js

به صورت آلترناتیو نسبت به متود های بالا شما میتوانید از متود fs.open() در باز کردن یک فایل برای خواندن یا نوشتن بر روی آن استفاده کنید.

سینتکس متود fs.open() به شرح زیر است:

fs.open(path, flags[, mode], callback)

توضیح پارامترهای سینتکس بالا:

path: آدرس کامل یا همان full path فایلی که قرار است باز شود. (در قالب رشته)

flag: پرچم عملیاتی که قرار است بر روی فایل صورت پذیرد. (در ادامه آموزش جدول کل پرچم های ممکن را می توانید مشاهده کنید)

mode: انتخاب حالت باز کردن فایل برای خواندن، نوشتن یا خواندن-نوشتن فایل.

callback: یک فانکشن است که دو پارامتر err و fd را دریافت می کند. پارامتر err حاوی پیغام های خطا و پارامتر fd حاوی اطلاعات درون فایل خواهد بود.

پرچم های قابل استفاده درون فانکشن های مرتبط با فایل در Node.js:

پرچم توضیحات
r یک فایل را برای خواندن باز می کند، اگر فایل موجود نباشد ارور ایجاد می شود.
r+ یک فایل را برای خواندن و نوشتن باز می کند، اگر فایل موجود نباشد ارور ایجاد می شود.
rs فایل را برای خواندن همگام باز می کند.
rs+ یک فایل را برای خواندن و نوشتن باز می کند، این فلگ به سیستم اعلام می کند که این کار را به صورت همگام انجام دهد.
w یک فایل را برای نوشتن باز می کند. اگر فایل موجود نباشد آن را می سازد، اگر فایل موجود باشد آن را ابتدا خالی می کند.
wx مثل w عمل می کند، اما اگر path موجود باشد ارور ایجاد خواهد شد.
w+ یک فایل را برای نوشتن و نوشتن باز می کند. اگر فایل موجود نباشد آن را می سازد، اگر فایل موجود باشد آن را ابتدا خالی می کند.
wx+ مثل w+ عمل می کند، اما اگر path موجود باشد ارور ایجاد خواهد شد.
a یک فایل را برای اضافه کردن چیزی به انتهای آن باز می کند.
ax مثل a عمل می کند، اما اگر path موجود باشد ارور ایجاد خواهد شد.
a+ یک فایل را برای اضافه کردن چیزی به انتهای آن و خواندن آن باز می کند. اگر فایل موجود نباشد ایجاد خواهد شد.
ax+ مثل a+ عمل می کند، اما اگر path موجود باشد ارور ایجاد خواهد شد.

مثال زیر یک فایل از قبل موجود را باز می کند و اطلاعات درون آن را می خواند:

var fs = require('fs');

fs.open('TestFile.txt', 'r', function (err, fd) {
    
                            if (err) {
                            return console.error(err);
    }
    
                            var buffr = Buffer.alloc(1024);
    
    fs.read(fd, buffr, 0, buffr.length, 0, function (err, bytes) {
       
                            if (err) throw err;
            
                            // Print only read bytes to avoid junk.
                            if (bytes > 0) {
            console.log(buffr.slice(0, bytes).toString());
        }
        
                            // Close the opened file.
        fs.close(fd, function (err) {
                            if (err) throw err;
        });
    });
});

فصل ۱۱ – مثال ۵

حذف فایل با ماژول fs در Node.js

حذف فایل ها در Node.js بسیار ساده انجام می شود اما در مورد حذف حتما مراقب باشید.

شما می توانید با استفاده از متود fs.unlink() یک فایل را از سیستم حذف کنیم.

سینتکس استفاده از متود fs.unlink() برای حذف فایلها در Node.js به شرح زیر است:

fs.unlink(path, callback);

مثال زیر نشان می دهد که چگونه می توانید یک فایل را در نود حذف کنید:

var fs = require('fs');

fs.unlink('test.txt', function () {
    
    console.log('deleting operation complete.');

});

فصل ۱۱ – مثال ۶

متود های قابل استفاده در ارتباط با فایل ها در Node.js

fs.readFile(fileName [,options], callback)

اطلاعات درون یک فایل که از قبل موجود است را می خواند.

fs.writeFile(filename, data[, options], callback)

اطلاعاتی را در یک فایل می نویسد. اگر فایل موجود باشد اطلاعات اولیه را پاک کرده و سپس متن را درون آن می نویسد و اگر فاییل موجود نباشد، ابتدا آن را ایجاد می کند و سپس اطلاعات را درون آن قرار می دهد.

fs.open(path, flags[, mode], callback)

فایل را برای خواندن و نوشتن باز می کند.

fs.rename(oldPath, newPath, callback)

یک فایل که از قبل وجود دارد را تغییر نام می دهد.

fs.chown(path, uid, gid, callback)

عملیات chown را به صورت نا همگام انجام می دهد.

fs.stat(path, callback)

آبجکت fs.stat را باز می گرداند که حاوی اطلاعات مهمی از فایل درخواستی است.

fs.link(srcpath, dstpath, callback)

به صورت ناهمگام فایل ها را لینک می کند.

fs.symlink(destination, path[, type], callback)

به صوت ناهمگام Symlink برای فایل ایجاد می کند.

fs.rmdir(path, callback)

یک دایکتوری که از قبل وجود دارد را تغییر نام می دهد.

fs.mkdir(path[, mode], callback)

یک دایرکتوری جدید را ایجاد می کند.

fs.readdir(path, callback)

محتوای یک دایرکتوری درخواستی را می خواند.

fs.utimes(path, atime, mtime, callback)

timestamp یک فایل را تغییر می دهد.

fs.exists(path, callback)

مشخص می کند آیا فایل درخواستی وجود دارد یا نه.

fs.access(path[, mode], callback)

قابلیت دسترسی کاربر به فایلی را مشخص می کند.

fs.appendFile(file, data[, options], callback)

مقداری متن جدید به انتهای فایلی که از قبل وجود دارد اضافه می کند.

تاریخ انتشار: پنجشنبه, ۱ خرداد ۱۳۹۹، ۱۲:۰۷ ق.ظ نویسنده: محمدیان

برنامه نویسی Node.js با ماژول ها، برنامه نوشته شده را بسیار خوانا تر می کند. معمولا ماژول فقط یک کار خاص را انجام می دهند و بار ها در طول اپلیکیشن مورد قابل استفاده قرار می گیرند. یک ماژول معمولا حاوی یک عملیات خاص می باشد که به صورت مستقل قادر به انجام هدف خود است.

در فصل قبلی ما دیدیم که چگونه می توانید با استفاده از module.exports یک ماژول لوکال را اکسپورت کنید (برای استفاده در تمامی اپلیکیشن، ماژول را معرفی کنید). اکنون می خواهیم نشان دهیم که چگونه می توانید ماژول های مختلف از انواع مختلف را با استفاده از module.exports اکسپورت کنید.

module.exports یا exports دو شی  خاص هستند که در تمامی فایلهای جاوا اسکریپت Node.js به طور پیش فرض وارد می شوند.

در module.exports درواقع کلمه module یک متغیر است که اشاره به ماژولی که درون آن هستیم دارد و کلمه exports یک شی است که این ماژول را برای کل اپلیکیشن به عنوان یک ماژول در دسترس معرفی خواهد کرد.

هر چیزی که شما به module.exports اختصاص دهید به عنوان یک ماژول به اپلیکیشن معرفی خواهد شد.

معرفی مقدار Literal به عنوان ماژول (خروجی گرفتن از Literal)

همانطور که در بالا اشاره کردیم exports یک شی یا همان object است. پس این شی هر چیزی که به آن اختصاص دهید را در اختیار کل اپلیکیشن قرار خواهد داد. پس در نتیجه اگر شما یک مقدار رشته را به این شیء اختصاص دهید آن رشته به عنوان یک ماژول به اپلیکیشن معرفی خواهد شد.

مثال زیر نشان می‌دهد که شما چگونه می توانید یک مقدار رشته را به عنوان یک ماژول به سیستم معرفی کنید. بهتر است فایلی به نام Message.js بسازید و فقط کد زیر را درون آن قرار دهید:

module.exports = 'Hello world';

//or

exports = 'Hello world';

فصل ۸ – مثال ۱ (فایل Message.js)

اکنون ماژولی که ساختید را درون فایل app.js با فانکشن require() فراخوانی کنید. مانند مثال زیر:

var msg = require('./Messages.js');

console.log(msg);

فصل ۸ – مثال ۱ (فایل app.js)

اگر دقت کنید قبل از اسم فایل نقطه و اسلش قرار گرفته است. ./ درواقع به همان فولدر که درون آن هستیم اشاره دارد. البته برای استفاده از ماژول های هسته ای نیاز به مشخص کردن path یا همان مسیر فایل ها ندارید.

برای اجرای مثال بالا می توانید در محیط ترمینال یا CMD دستور node app.js را تایپ کرده و اینترنت بزنید. (دقت کنید که در محیط ترمینال ابتدا باید با دستور cd به مکانی که فایل را ساخته اید نقل مکان کنید.)

معرفی Object به عنوان ماژول (خروجی گرفتن از Object)

exports یک object است پس می توانید به آن صفت یا متود اختصاص دهید.

مثال زیر نشان می دهد که شما چگونه می توانید یک رشته را به عنوان صفت به یک آبجکت اختصاص دهید و object را اکسپورت کنید. بهتر است فایلی به نام Message.js ساخته و کد زیر را درون آن کپی کنید:

exports.SimpleMessage = 'Hello world';

//or

module.exports.SimpleMessage = 'Hello world';

فصل ۸ – مثال ۲ (فایل Message.js)

در مثال بالا ما صفت SimpleMessage را به شی export اختصاص داده ایم.

شما اکنون می توانید ماژول ساخته شده را در فایل app.js به شکل زیر مورد استفاده قرار دهید:

var msg = require('./Messages.js');

console.log(msg.SimpleMessage);

فصل ۸ – مثال ۲ (فایل app.js)

در مثال بالا فانکشن require() آبجکت { SimpleMessage : 'Hello World'} را باز می گرداند. پس شما می توانید با استفاده از نام متغیر اختصاص داده شده به این ماژول، صفت های درون آن را صدا بزنید. مانند: msg.SimpleMessage

مثال بالا را با استفاده از node app.js در ترمینال اجرا کنید.

به همین سبک بالا می توانید یک فانکشن را به همراه آبجکت اکسپورت کنید.

مثال زیر نشان می دهد که شما چگونه می توانید فانکشن لاگ را به همراه یک شی به عنوان یک ماژول اکسپورت کنید، این کد ها را در فایل Log.js ذخیره کنید:

module.exports.log = function (msg) { 
    console.log(msg);
};

فصل ۸ – مثال ۳ (فایل Log.js)

مثال بالا در واقع آبجکت { log : function(msg){ console.log(msg); } } را در هنگام استفاده از require() باز خواهد گرداند. پس شما می توانید با استفاده از نام متغیر اختصاص داده شده به متود های درونی آن دسترسی پیدا کنید.

به مثال زیر توجه کنید، این کد ها محتویات فایل app.js است:

var msg = require('./Log.js');

msg.log('Hello World');

فصل ۸ – مثال ۳ (فایل app.js)

مثال بالا را با استفاده از node app.js در ترمینال اجرا کنید.

شما می توانید به روش زیر نیز یک object را به عنوان ماژول اکسپورت کنید. بهتر است فایلی به نام data.js ساخته و متن زیر را درون آن کپی کنید:

module.exports = {
    firstName: 'James',
    lastName: 'Bond'
}

فصل ۸ – مثال ۴ (فایل data.js)

برای فراخوانی این ماژول می توانید در فایل app.js کد زیر را استفاده کنید:

var person = require('./data.js');
console.log(person.firstName + ' ' + person.lastName);

فصل ۸ – مثال ۴ (فایل app.js)

مثال بالا را با استفاده از node app.js در ترمینال اجرا کنید.

معرفی فانکشن به عنوان ماژول (خروجی گرفتن از Function)

به روش زیر شما قادر هستید تا یک فانکشن را به عنوان یک ماژول export کنید. پیشنهاد می شود فایلی به نام Log.js ساخته و کد زیر را درون آن کپی کنید:

module.exports = function (msg) { 
    console.log(msg);
};

فصل ۸ – مثال ۵ (فایل Log.js)

اکنون می توانید با کد زیر ماژول را درون فایل app.js فراخوانی کنید:

var msg = require('./Log.js');

msg('Hello World');

فصل ۸ – مثال ۵ (فایل app.js)

در مثال بالا متغیر msg به یک عبارت عملیاتی یا همان Function Expression تبدیل خواهد شد. پس شما می توانید با استفاده از پرانتز بعد از نام متغیر، فانکشن را اجرا کنید.

مثال بالا را با استفاده از node app.js در ترمینال اجرا کنید.

معرفی فانکشن به عنوان یک کلاس

در زبان جاوا اسکریپت می توان با یک فانکشن به عنوان یک کلاس برخورد کرد.

مثال زیر نشان می دهد که شما چگونه می توانید یک فانکشن را مانند یک کلاس export کنید. این محتویات را درون فایل Person.js کپی کنید:

module.exports = function (firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.fullName = function () { 
        return this.firstName + ' ' + this.lastName;
    }
}

فصل ۸ – مثال ۶ (فایل Person.js)

ماژول ساخته شده بالا را می توان به روش زیر در فایل app.js مورد استفاده قرار داد:

var person = require('./Person.js');

var person1 = new person('James', 'Bond');

console.log(person1.fullName());

فصل ۸ – مثال ۶ (فایل app.js)

مثال بالا را با استفاده از node app.js در ترمینال اجرا کنید تا نتیجه James Bond را همان جا مشاهده کنید.

بارگذاری ماژول ها از پوشه های دیگر

برای بارگذاری ماژول ها، در هر پوشه ای که باشند، در فانکشن require() فقط کافی است تا از full path یا همان آدرس کامل فایل استفاده کنید.

به عنوان مثال اگر فایلی که در آن از فانکشن require() استفاده کرده اید ماژولی که در فولدر utility قرار داده شده باشد را فراخوانی می کند، به این صورت عمل کنید:

var log = require('./utility/Log.js');

فصل ۸ – مثال ۷ (فایل app.js)

در مثال بالا نقطه . مشخص می کند که فولدر utility کنار همین فایلی که در حال اجرای require هست، وجود دارد.

بارگذاری ماژول ها فقط با نام پوشه ای که درون آن قرار دارند

Node.js به شما اجازه می دهد تا فقط با اشاره به نام فولدری که ماژول درون آن قرار دارد، ماژول را فراخوانی کنید. البته مراحل کمی متفاوت است.

مانند مثال زیر که شبیه مورد بالا عمل می کند:

var log = require('./utility');

فصل ۸ – مثال ۸ (فایل app.js)

این مرحله اول کار بود. هنگامی که از نام پوشه استفاده می کنید، Node.js بنا را بر این می گذارد که درون این پوشه یک پکیج وجود دارد. برای اینکه شما Node.js را برای درک بهتر این پکیج راهنمایی کنید، باید فایلی به نام package.json را درون پوشه utility قرار دهید. با محتویات زیر:

{
    "name" : "log",
    "main" : "./Log.js"
}

فصل ۸ – مثال ۸ (فایل package.json)

درواقع این کلمه کلیدی “main” مشخص می کند که فایل اصلی که پکیج ما به آن اشاره دارد کدام فایل است و همچنین نام پکیج را کلمه کلیدی “name” مشخص می کند.

اگر فایل package.json وجود نداشته باشد، Node.js به صورت اتوماتیک فایل index.js را به عنوان فایل ماژول در نظر می گیرد.

منبع: نتران

تاریخ انتشار: پنجشنبه, ۱ خرداد ۱۳۹۹، ۱۲:۰۳ ق.ظ نویسنده: محمدیان

Node.js به برنامه نویسان اجازه می دهد تا رویداد های دلخواه خود را ایجاد کنند و هر بار که خواستند آنها را به اجرا درآورند. ماژول events حاوی کلاس EventEmitter است که می‌تواند رویداد ها را ایجاد کرده و آنها را مدیریت کند. فصل سیزدهم آموزش نود.جی اس نتران، به نحوه استفاده از ماژول events برای به اجرا درآوردن یک رویداد اختصاص دارد.

مثال زیر نشان می‌دهد که شما چگونه می‌توانید از کلاس EventEmitter برای ساخت یک رویداد دلخواه استفاده کنید:

// get the reference of EventEmitter class of events module
var events = require('events');

//create an object of EventEmitter class by using above reference
var em = new events.EventEmitter();

//Subscribe for FirstEvent
em.on('FirstEvent', function (data) {
    console.log('First subscriber: ' + data);
});

// Raising FirstEvent
em.emit('FirstEvent', 'This is my first Node.js event emitter example.');

 

در مثال بالا ما ابتدا این ماژول events را ایمپورت کرده و سپس یک شی از کلاس EventEmitter ایجاد می کنیم.

سپس با استفاده از متود on() یک فانکشن مدیریت کننده برای رویداد مورد نظر تعریف می کنیم.

فانکشن on() نام رویداد را به عنوان پارامتر اول و فانکشن مدیریت در صورت رخداد رویداد را به عنوان پارامتر دوم دریافت می کند.

فانکشن emit() یک رویداد را اجرا می کند. در پارامتر اول نام رویداد را باید به این فانکشن بدهید و به عنوان پارامتر دوم می توانید اطلاعات اضافی را به رویداد انتقال دهید.

همچنین شما می توانید با استفاده از متود addListener() در صورت ایجاد رخداد مورد نظر یک فانکشن را اجرا کنید. مانند مثال زیر:

var emitter = require('events').EventEmitter;

var em = new emitter();

//Subscribe FirstEvent
em.addListener('FirstEvent', function (data) {
    console.log('First subscriber: ' + data);
});

//Subscribe SecondEvent
em.on('SecondEvent', function (data) {
    console.log('First subscriber: ' + data);
});

// Raising FirstEvent
em.emit('FirstEvent', 'This is my first Node.js event emitter example.');

// Raising SecondEvent
em.emit('SecondEvent', 'This is my second Node.js event emitter example.');

متود های مهم کلاس EventEmitter

emitter.addListener(event, listener)

این متود یک listener برای یک رویداد خاص به آخر آرایه listener ها اضافه می کند. توجه کنید که بررسی اینکه آیا این listener از قبل وجود دارد یا نه انجام نمی شود.

emitter.on(event, listener)

این متود یک listener برای یک رویداد خاص به آخر آرایه listener ها اضافه می کند. توجه کنید که بررسی اینکه آیا این listener از قبل وجود دارد یا نه انجام نمی شود. از این متود به جای متود addListener می توان استفاده کرد.

emitter.once(event, listener)

این متود فقط برای یک بار، یک رویداد را به اجرا در می آورد.

emitter.removeListener(event, listener)

یک listener را برا یک رویداد خاص حذف می کند.

emitter.removeAllListeners([event])

تمام listener ها را برای یک رویداد خاص حذف می کند.

emitter.setMaxListeners(n)

به طور پیشفرض EventEmitter ها بعد از اضافه کردن ۱۰ عدد listener پیغام هشدار چاپ می کنند.

emitter.getMaxListeners()

مقدار نهایی تعداد listener های ممکن برای یک eventEmiter را باز می گرداند. چه با emitter.setMaxListeners(n) ست شده باشد، چه به صورت پیشفرض

emitter.listeners(event)

یک کپی از آرایه listener ها باز می گرداند.

emitter.emit(event[, arg1][, arg2][, …])

یک رویداد خاص را به اجرا در می آورد.

emitter.listenerCount(type)

تعداد listener هایی که به این نوع رویداد واکنش نشان می دهند را از می گرداند.

روش های متداول استفاده از EventEmitter ها

دو روش متداول برای استفاده از EventEmitter ها در Node.js وجود دارد:

  • بازگرداندن EventEmitter در یک فانکشن
  • توسعه دادن کلاس EventEmitter

بازگرداندن EventEmitter در یک فانکشن

فانکشن سازنده آبجکتی از کلاس EventEmitter را باز می گرداند که برای مدیریت رویداد ها استفاده شده است.

این آبجکت EventEmitter را می توان برای مشترک شدن در رویداد ها استفاده کرد. (یعنی هنگامی که رویداد رخ داد، فانکشنی اجرا شود)

به عنوان مثال به کد زیر توجه کنید:

var emitter = require('events').EventEmitter;

function LoopProcessor(num) {
    var e = new emitter();
    
    setTimeout(function () {
        
        for (var i = 1; i <= num; i++) {
            e.emit('BeforeProcess', i);
            
            console.log('Processing number:' + i);
            
            e.emit('AfterProcess', i);
        }
    }
    , 2000)
    
    return e;
}
var lp = LoopProcessor(3);

lp.on('BeforeProcess', function (data) {
    console.log('About to start the process for ' + data);
});

lp.on('AfterProcess', function (data) {
    console.log('Completed processing ' + data);
});

فصل ۱۳ - مثال ۳

پاسخ اجرای کد بالا چنین خواهد بود:

About to start the process for 1 
Processing number:1
Completed processing 1
About to start the process for 2
Processing number:2
Completed processing 2
About to start the process for 3
Processing number:3
Completed processing 3

در فانکشن LoopProcessor() ابتدا ما یک شی از کلاس EventEmitter ایجاد کرده ایم رویدادهای 'BeforeProcess' و 'AfterProcess' را emit کرده ایم. (emit کردن یعنی اجرا یا انتشار یک رویداد)

در نهایت در این فانکشن با Object ساخته شده از کلاس EventEmitter را باز می گردانیم.

پس ما قادر هستیم با کمک متغیر lp که به فانکشن LoopProcessor() اشاره دارد و اکنون آبجکتی از کلاس EventEmitter است در رویدادها مشترک شویم. اشتراک در رویدادها به کمک فانکشن on() انجام می شود.

به جای فانکشن on() از فانکشن addListener() نیز می توان استفاده کرد.

توسعه دادن کلاس EventEmitter

در این روش مدیریت رویدادها باید در یک فانکشن سازنده کلاس EventEmitter را توسعه دهید و رویدادها را در آن مدیریت کنید.

به مثال زیر توجه کنید:

var emitter = require('events').EventEmitter;

var util = require('util');

function LoopProcessor(num) {
    var me = this;

    setTimeout(function () {
        
        for (var i = 1; i <= num; i++) {
            me.emit('BeforeProcess', i);
            
            console.log('Processing number:' + i);
            
            me.emit('AfterProcess', i);
        }
    }
    , 2000)
        
    return this; 
}

util.inherits(LoopProcessor, emitter)

var lp = new LoopProcessor(3);

lp.on('BeforeProcess', function (data) {
    console.log('About to start the process for ' + data);
});

lp.on('AfterProcess', function (data) {
    console.log('Completed processing ' + data);
});

پاسخ اجرای کد بالا چنین خواهد بود:

About to start the process for 1 
Processing number:1
Completed processing 1
About to start the process for 2
Processing number:2
Completed processing 2
About to start the process for 3
Processing number:3
Completed processing 3 

در مثال بالا ما فانکشن LoopProcessor را با کلاس EventEmitter به وسیله متود util.inherits() گسترش داده ایم. پس this در مثال بالا به کلاس EventEmitter اشاره دارد.

 

منبع : نتران netrun.ir

تاریخ انتشار: چهارشنبه, ۳۱ ارديبهشت ۱۳۹۹، ۱۱:۱۵ ب.ظ نویسنده: محمدیان

حلقه Event یکی از مهم‌ترین جنبه‌های جاوا اسکریپت است که باید درک شود. در این بخش از مقالات آموزش Node.js جزییات دقیق طرز کار جاوا اسکریپت با «نخ» (Thread) منفرد و شیوه مدیریت تابع‌های ناهمگام را مورد بررسی قرار می‌دهیم.

به طور کلی، در اغلب مرورگرها یک حلقه Event روی هر برگه مرورگر وجود دارد که موجب می‌شود هر پردازشی مجزا باشد و از مسدودسازی کل مرورگر در نتیجه یک پردازش سنگین یا حلقه بی‌نهایت جلوگیری شود. این محیط چندین حلقه Event همزمان را مدیریت می‌کند تا برای نمونه فراخوانی‌های API را مدیریت کند. بدین ترتیب Web Workers در حلقه رویداد خودشان اجرا می‌شوند. شما به صورت عمده باید در مورد این نگران باشید که کد روی یک حلقه رویداد منفرد اجرا خواهد شد و در هنگام نوشتن کد این ذهنیت جلوگیری از مسدودسازی را مداوماً داشته باشید.

مسدودسازی حلقه Event

هر کد جاوا اسکریپتی که بازگرداندن کنترل به حلقه event را بیش از حد طول بدهد، موجب مسدود شدن اجرای کد جاوا اسکریپت در صفحه خواهد شد و حتی ممکن است نخ UI را مسدود کند. در این حالت کاربر دیگر نمی‌تواند روی چیزی کلیک کند و یا کارهایی مانند اسکرول و نظایر آن انجام دهد.

تقریباً همه کارهای ابتدایی I/O در جاوا اسکریپت غیر مسدودکننده هستند. بنابراین درخواست‌های شبکه، عملیات فایل سیستم Node.js و مواردی از این دست همگی غیر مسدودکننده هستند. مسدودکننده بودن یک استثنا است و به همین دلیل جاوا اسکریپت به طور عمده بر مبنای callback-ها کار می‌کند. البته در نسخه‌های اخیر تمرکز بیشتر روی promises و async/await انتقال یافته است.

پشته فراخوانی

پشته فراخوانی یک صف LIFO به معنی «ورودی آخر، خروجی اول» (Last In ،First Out) است. حلقه Event به طور پیوسته پشته فراخوانی را بررسی می‌کند تا ببیند آیا هیچ تابعی نیاز به اجرا دارد یا نه. در زمانی که چنین نیازی باشد، هر فراخوانی تابعی را که می‌یابد به پشته فراخوانی اضافه می‌کند تا به ترتیب اجرا شوند. همه شما «رد پشته خطا» (Error Stack Trace) را می‌شناسید و آن را در دیباگر یا در کنسول مرورگر دیده‌اید.

مرورگر نام‌های تابع‌ها را در پشته فراخوانی بررسی می‌کند تا مطلع شود که کدام تابع فراوانی جاری را آغاز کرده است.

توضیح ساده حلقه Event

به مثال زیر توجه کنید:

const bar = () => console.log('bar')
const baz = () => console.log('baz')
const foo = () => { console.log('foo') bar() baz()}
foo()

//----------
Foobarbaz

 

که مطابق انتظار است. زمانی که این کد اجرا می‌شود ابتدا ()foo فراخوانی می‌شود. درون ()foo ابتدا ()bar را فراخوانی می‌کنیم و سپس ()baz فراخوانی می‌شود. در این مرحله پشته فراخوانی مانند زیر است:

 

حلقه Event در هر تکرار بررسی می‌کند که آیا چیزی در پشته فراخوانی وجود دارد یا نه و آن را اجرا می‌کند:

تا این که پشته فراخوانی خالی شود.

صف‌بندی اجرای تابع

مثال فوق معمولی به نظر می‌رسد و نکته خاصی ندارد: جاوا اسکریپت مواردی که باید اجرا شوند را می‌یابد و آن‌ها را به ترتیب اجرا می‌کند. در ادامه با روش به تعویض انداختن (defer) یک تابع تا زمان خالی شدن پشته آشنا می‌شویم. کاربرد دستور زیر برای فراخوانی یک تابع است:

setTimeout(() => {})، 0)

اما هر بار که تابع دیگری در کد اجرا شود، این دستور نیز اجرا خواهد شد. مثال زیر را در نظر بگیرید:

const bar = () => console.log('bar')
const baz = () => console.log('baz')
const foo = () => { console.log('foo') setTimeout(bar، 0) baz()}
foo()

شاید شگفت‌زده شوید که کد فوق عبارت زیر را در خروجی نمایش می‌دهد:

Foobazbar

زمانی که این کد اجرا شود، ابتدا ()foo فراخوانی می‌شود. درون ()foo ابتدا setTimeout فراخوانی می‌شود و bar به عنوان یک آرگومان ارسال می‌شود. ما آن را طوری تنظیم می‌کنیم تا حد امکان به سرعت اجرا شود و مقدار 0 به عنوان تایمر ارسال می‌شود سپس ()baz را فراخوانی می‌کنیم. در این نقطه پشته فراخوانی مانند زیر خواهد بود:

ترتیب اجرای تابع‌ها

در ادامه ترتیب اجرای همه تابع‌ها را در برنامه مشاهده می‌کنید:

صف پیام

زمانی که ()setTimeout فراخوانی می‌شود، مرورگر یا Node.js تایمر را آغاز می‌کند. زمانی که تایمر منقضی شود، در این حالت از آنجا که مقدار 0 به عنوان timeout تعیین شده است، تابع callback در «صف پیام» (Message Queue) قرار می‌گیرد.

صف پیام جایی است که رویدادهای آغاز شده از سمت کاربر مانند رویدادهای کلیک و ضربه‌های کیبورد و یا واکشی پاسخ‌ها از صف پیش از آن که کد فرصت واکنش به آن‌ها را داشته باشد صف‌بندی می‌شوند. رویدادهای DOM مانند onLoad نیز چنین خصوصیتی دارند.

این حلقه به پشته فراخوانی اولویت می‌دهد. این حلقه ابتدا همه چیز که در پشته فراخوانی بیاید پردازش می‌کند و زمانی که چیزی باقی نماند، اقدام به انتخاب موارد موجود در صف پیام می‌کند.

بدین ترتیب لازم نیست برای تابع‌هایی مانند setTimeout منتظر بمانیم یا این که صبر کنیم واکشی یا دیگر امور به پایان برسند، زیرا از سوی مرورگر ارائه شده‌اند و روی نخ‌های خود زنده هستند. برای نمونه اگر مقدار timeout را با استفاده از دستور setTimeout روی 2 ثانیه تنظیم کرده باشید، لازم نیست 2 ثانیه منتظر بمانید چون انتظار در هر جایی رخ می‌دهد.

صف کار در ES6

استاندارد ECMAScript 2015 مفهوم «صف کار» (Job Queue) را معرفی کرده است که از سوی Pomise-ها مورد استفاده قرار می‌گیرد و روشی برای اجرای نتیجه یک تابع async به محض امکان است و دیگر آن را در انتهای پشته فراخوانی قرار نمی‌دهیم. بدین ترتیب Promise-هایی که پیش از اتمام تابع جاری خاتمه یابند، درست پس از تابع جاری اجرا خواهند شد.

برای مقایسه می‌توانید یک قطار هوایی شهر بازی را در نظر بگیرید. صف پیام شما را در پشت همه افرادی که قبل از شما در صف جای گرفته‌اند قرار می‌دهد، در حالی که صف کار یک بلیت سریع‌السیر است که اجازه می‌دهد درست پس از اتمام یک دور، مجدداً بی‌درنگ سوار قطار هوایی شوید.

مثال:

const bar = () => console.log('bar')
const baz = () => console.log('baz')
const foo = () => { console.log('foo') setTimeout(bar، 0) new Promise((resolve، reject) => resolve('should be right after baz، before bar')).then(resolve => console.log(resolve)) baz()}
foo()

کد فوق عبارت زیر را نمایش می‌دهد:

foobazshould be right after foo، before barbar

این تفاوت بزرگی است که بین Promise-ها (و البته async/await که بر مبنای Promise ساخته شده) با تابع‌های ساده قدیمی ناهمگام که از طریق ()setTimeout یا دیگر API-های پلتفرم اجرا می‌شدند وجود دارد.

درک ()process.nextTick

زمانی که تلاش می‌کنید حلقه رویداد Node.js را درک کنید، یک بخش مهم آن ()process.nextTick است. این بخش با حلقه رویداد به روشی خاص تعامل پیدا می‌کند. هر بار که حلقه رویداد یک دور کامل می‌زند آن را یک tick می‌نامیم.

زمانی که یک تابع را به ()process.nextTick ارسال می‌کنیم به موتور مرورگر دستور می‌دهیم که تابع را در انتهای عملیات جاری و پیش از آغاز تیک بعدی حلقه رویداد احضار کند:

process.nextTick(() => {//do something})

حلقه رویداد مشغول پردازش کردن کد تابع جاری است. زمانی که این عملیات پایان گیرد، موتور جاوا اسکریپت همه تابع‌های ارسالی در فراخوانی‌های nextTick که در طی اجرای این عملیات ارسال شده‌اند را اجرا می‌کند. به این ترتیب به موتور جاوا اسکریپت اعلام می‌کنیم که یک تابع را به روشی ناهمگام (پس از اجرای تابع جاری) اما به سرعت و بدون صف‌بندی پردازش کند. فراخوانی کردن (setTimeout(() => {}، 0 موجب می‌شود که تابع در تیک بعدی و بسیار بعدتر از زمانی که از ()nextTick استفاده می‌کنیم اجرا شود. از ()nextTick زمانی استفاده کنید که می‌خواهید مطمئن شوید در تکرار بعدی حلقه رویداد، کد حتماً اجرا خواهد شد.

درک ()setImmediate

هنگامی که می‌خواهیم بخشی از کد را به صورت ناهمگام اجرا کنیم، اما این کار در سریع‌ترین زمان ممکن صورت گیرد، یک گزینه استفاده از تابع ()setImmediate است که از سوی Node.js ارائه شده است:

setImmediate(() => {//run something})

هر تابعی که به صورت آرگومان ()setImmediate ارسال شود یک callback است که در تکرار بعدی حلقه رویداد اجرا خواهد شد. اینک شاید از خود بپرسید ()setImmediate چه تفاوتی با (setTimeout(() => {}، 0 و یا ()process.nextTick دارد؟ تابعی که به ()process.nextTick ارسال شود در تکرار بعدی حلقه رویداد و پس از پایان یافتن عملیات اجرا خواهد شد. این بدان معنی است که همواره پیش از ()setTimeout و ()setImmediate اجرا می‌شود. یک callback به نام ()setTimeout با تأخیر 0 میلی‌ثانیه بسیار به ()setImmediate شباهت دارد. ترتیب اجرا به عوامل مختلفی وابسته خواهد بود، اما هر دوی آن‌ها در تکرار بعدی حلقه رویداد اجرا خواهند شد.

تایمرها

زمانی که کد جاوا اسکریپت می‌نویسیم ممکن است بخواهیم اجرای یک تابع را به تأخیر بیندازیم. به این منظور می‌توان از ()setTimeout و ()setInterval برای زمان‌بندی اجرای تابع در آینده استفاده کرد.

()setTimeout

هنگامی که کد جاوا اسکریپت می‌نویسیم، می‌توانیم با استفاده از دستور ()setTimeout اجرای یک تابع را به تأخیر بیندازیم. می‌توان یک تابع callback تعیین کرد که بعدتر اجرا شود و مقداری برای میزان این تأخیر در اجرا بر مبنای میلی‌ثانیه تعیین کرد:

setTimeout(() => {// runs after 2 seconds}، 2000)
setTimeout(() => {// runs after 50 milliseconds}، 50)

این ساختار یک تابع جدید تعریف می‌کند. شما می‌توانید تابع دیگر را درون آن فراخوانی کنید یا این که نام یک تابع موجود را به آن ارسال و پارامترهای آن را تعیین کنید:

const myFunction = (firstParam، secondParam) => {// do something}
// runs after 2 secondssetTimeout(myFunction، 2000، firstParam، secondParam)

()setTimeout یک شناسه تایمر بازگشت می‌دهد. این شناسه عموماً استفاده‌ای ندارد، اما می‌توانید آن را ذخیره کنید و در صورتی که بخواهید اجرای این تابع زمان‌بندی‌شده را حذف کنید آن را پاک کنید:

const id = setTimeout(() => {// should run after 2 seconds}، 2000)
// I changed my mindclearTimeout(id)

تأخیر صفر

اگر میزان تأخیر را برابر با 0 تعیین کنید، تابع callback در اولین فرصت ممکن، اما پس از اجرای تابع جاری اجرا خواهد شد:

setTimeout(() => { console.log('after ')}، 0)
console.log(' before ')

کد فوق عبارت زیر را نمایش می‌دهد:

before after

این حالت به طور خاص در مواردی که می‌خواهیم از مسدود شدن CPI روی وظایف سنگین جلوگیری کنیم و اجازه دهیم تابع‌های دیگر نیز در زمان اجرای یک محاسبه سنگین اجرا شوند مفید خواهد بود. این کار از طریق صف‌بندی تابع‌ها در یک جدول زمان‌بندی ممکن خواهد بود. برخی مرورگرها (مانند IE و Edge) یک متد ()setImmediate را پیاده‌سازی کرده‌اند که دقیقاً همین کارکرد را انجام می‌دهد، اما استاندارد نیست و روی مرورگرهای دیگر وجود ندارد. اما این تابع که معرفی کردیم در Node.js یک استاندارد محسوب می‌شود.

()setInterval

()setInterval یک تابع مشابه ()setTimeout است و تنها یک تفاوت دارد. به جای اجرا کردن یک‌باره تابع callback این تابع آن را برای همیشه و در بازه‌های زمانی معین شده (بر حسب میلی‌ثانیه) اجرا می‌کند:

setInterval(() => {// runs every 2 seconds}، 2000)

تابع فوق هر 2 ثانیه یک بار اجرا می‌شود، مگر اینکه با استفاده از clearInterval و ارسال شناسه بازه‌ای که در پی اجرای setInterval بازگشت می‌یابد از آن بخواهیم متوقف شود:

const id = setInterval(() => {// runs every 2 seconds}، 2000)
clearInterval(id)

فراخوانی clearInterval درون تابع callback setInterval رویه‌ای رایج است و بدین ترتیب می‌توان در مورد اجرای مجدد یا توقف آن یک تصمیم‌گیری خودکار داشت. برای نمونه کد زیر کار دیگری را اجرا می‌کند، مگر اینکه App.somethingIWait دارای مقدار arrived باشد:

const interval = setInterval(function() { if (App.somethingIWait === 'arrived') { clearInterval(interval)
// otherwise do things }}، 100

setTimeout بازگشتی

setTimeout یک تابع را هر n میلی‌ثانیه یک بار اجرا می‌کند و هیچ ملاحظه‌ای در مورد زمان پایان اجرای تابع ندارد. اگر یک تابع همواره زمان مشابهی را نیاز داشته باشد، این روش مناسب خواهد بود:

اما ممکن است تابع برای نمونه بسته به شرایط شبکه، برای اجرا به زمان متفاوتی نیاز داشته باشد:

و حتی ممکن است این زمان طولانی اجرا با تابع بعدی همپوشانی پیدا کند:

تعیین setTimout بازگشتی

برای جلوگیری از این وضعیت می‌توان یک setTimeout بازگشتی زمان‌بندی کرد تا زمانی که تابع callback به پایان می‌رسد فراخوانی شود:

const myFunction = () => {// do something
setTimeout(myFunction، 1000)}
setTimeout(myFunction()}، 1000)

برای دستیابی به این سناریو:

 

setTimeout و setInterval هر دو در Node.js از طریق ماژول Timers در اختیار ما قرار گرفته‌اند. ()Node.js تابع setImmediate را نیز ارائه کرده است که معادل استفاده از دستور زیر است:

setTimeout(() => {}، 0)ا

 

تاریخ انتشار: چهارشنبه, ۳۱ ارديبهشت ۱۳۹۹، ۱۰:۳۷ ب.ظ نویسنده: محمدیان

به دلیل اینکه با مباحث مهمی روبرو هستیم، سعی کردم یه منبع پیدا کنم و راجع این مباحث یه مرجع کامل تر داشته باشیم. توجه کنید که ترتیب مباحث از ابتدای صفحه تا انتهای صفحه ست. اول callback رو بفمید و سپس promise و در آخر async و await .

جاوا اسکریپت به صورت پیش‌فرض یک زبان برنامه‌نویسی همگام و تک نخی است. این بدان معنی است که کد نمی‌تواند نخ‌های جدیدی ایجاد کرده و به صورت موازی اجرا شود. در این مقاله به بررسی روش‌های مختلف برنامه نویسی ناهمگام در محیط Node.js می‌پردازیم.

ناهمگامی در زبان‌های برنامه‌نویسی

رایانه‌ها اساساً دارای طراحی ناهمگام هستند. منظور از ناهمگامی این است که کارهای مختلف می‌توانند در گردش برنامه اصلی مستقل از هم اتفاق بیفتند. در رایانه‌های امروز هر برنامه‌ای برای مدت زمان معینی اجرا می‌شود و سپس اجرای آن متوقف می‌شود تا برنامه دیگری بتواند به اجرای خود ادامه بدهد. این کار در چنان چرخه سریعی اتفاق می‌افتد که کاربر هرگز متوجه نمی‌شود و ما فکر می‌کنیم رایانه ما می‌تواند به طور همزمان برنامه‌های زیادی را اجرا کند، اما این یک توهم است (البته قضیه در پردازنده‌های چندهسته‌ای متفاوت است).

برنامه‌ها به صورت داخلی از «وقفه» (interrupt) استفاده می‌کنند. وقفه یک سیگنال است که به سمت پردازنده ارسال می‌شود تا توجه سیستم را کسب کند. پرداختن به جزییات این موضوع خارج از موضوع این مقاله است؛ اما باید به خاطر داشته باشید که ناهمگامی در برنامه‌ها امری معمول است و به طور نرمال برنامه‌ها، اجرای خود را متوقف می‌کنند تا زمانی که پردازنده به آن‌ها وقتی اختصاص بدهد. زمانی که یک برنامه منتظر پاسخی از سوی شبکه است، نمی‌تواند پردازنده را معطل خود نگه دارد تا درخواست پایان یابد.

به طور معمول زبان‌های برنامه‌نویسی همگام هستند. برخی از آن‌ها نیز در حد زبان یا کتابخانه‌های جانبی، روش‌هایی برای مدیریت ناهمگامی ارائه می‌کنند. زبان‌های C ،Java ،C# ،PHP ،Go ،Ruby ،Swift و Python همگی به صورت پیش‌فرض همگام هستند. برخی از این زبان‌ها با استفاده از نخ‌ها به صورت ناهمگام عمل می‌کنند و می‌توانند یک پردازش جدید ایجاد کنند.

جاوا اسکریپت

زبان برنامه‌نویسی جاوا اسکریپت نیز به صورت پیشفرض همگام و تک نخی است. این بدان معنی است که کد نمی‌تواند نخ‌های جدید ایجاد کرده و به صورت موازی اجرا شود. در این حالت، خطوط مختلف کد یکی پس از دیگری و به ترتیب اجرا می‌شوند. برای نمونه:

const a = 1const b = 2const c = a * bconsole.log(c)doSomething()

اما می‌دانیم که جاوا اسکریپت درون مرورگر تولد یافته است و وظیفه اصلی آن در ابتدا پاسخ دادن به واکنش‌های کاربر مانند onClick ،onMouseOver ،onChange ،onSubmit و غیره بوده است. چنین فرایندی در یک مدل برنامه‌نویسی همگام چگونه ممکن است؟

پاسخ در محیط برنامه‌نویسی این زبان است. مرورگر روشی برای ارائه یک مجموعه از API-ها دارد که می‌توانند این نوع کارکرد را مدیریت کنند. در سال‌های اخیر Node.js یک محیط غیر مسدودکننده I/O معرفی کرده است که این مفهوم را به دسترسی فایل‌ها، فراخوانی‌های شبکه و موارد مشابه گسترش داده است.

Callback-ها

ما هرگز نمی‌دانیم که یک کاربر چه زمانی روی یک دکمه کلیک خواهد کرد، بنابراین باید یک «دستگیره رویداد» (event handler) برای رویداد کلیک تعریف کنیم.

این دستگیره رویداد تابعی قبول می‌کند که هنگام اتفاق افتادن رویداد فراخوانی می‌شود:

document.getElementById('button').addEventListener('click'، () => {//item clicked})

این همان callback مشهور است. در واقع callback تابع ساده‌ای است که به صورت یک مقدار به تابع دیگر ارسال می‌شوند و تنها زمانی اجرا خواهد شد که رویدادی اتفاق بیفتد. ما این کار را به این دلیل می‌توانیم اجرا کنیم که جاوا اسکریپت تابع‌های درجه نخست دارد که می‌توانند به متغیرها انتساب یافته و به تابع‌های دیگر (که تابع‌های درجه بالا نامیده می‌شوند) ارسال شوند.

رویه معمول این است که کد کلاینت در یک شنونده رویداد load در شیء window قرار می‌گیرد و تابع callback را در زمان آماده شدن صفحه اجرا می‌کند:

	
window.addEventListener('load'، () => {//window loaded //do what you want}

Callback-ها در همه جا استفاده می‌شوند و اختصاص به رویدادهای DOM ندارند. یک مثال عمومی استفاده از تایمر است:

setTimeout(() => {// runs after 2 seconds}، 2000

درخواست‌های XHR نیز یک callback می‌پذیرند. در مثال زیر یک تابع به مشخصه‌ای که در زمان رخداد اتفاق خاص فراخوانی می‌شود، انتساب خواهد یافت:

const xhr = new XMLHttpRequest() xhr.onreadystatechange = () => {
    if (xhr.readyState === 4) {
        xhr.status === 200 ? console.log(xhr.responseText) : console.error('error')
    }
}
xhr.open('GET', 'https://yoursite.com') xhr.send()

مدیریت خطا در Callback-ها

یکی از رایج‌ترین راهبردها برای مدیریت خطا در callback-ها کاری است که Node.js انجام می‌دهد. پارامتر اول در هر تابع callback شیء خطا است که به نام callback-های error-first شناخته می‌شوند.

اگر خطایی وجود نداشته باشد، این شیء null خواهد بود. اگر خطایی رخ داده باشد این پارامتر نوعی توضیح در مورد خطا و اطلاعات مرتبط ارائه می‌کند.

fs.readFile('/file.json'، (err، data) => { if (err!== null) {//handle error console.log(err) return }

مشکل Callback-ها

به جای استفاده از کال بک میتوانیم کد خود را با پرامیس بنویسیم و یا کدهایی که به صورت کال بک نوشته شده تبدیل به پرامیس کنیم تا بتوانیم در آینده از مزایای async/await هم استفاده کنیم

Callback-ها در استفاده‌های ساده بسیار عالی هستند. اما هر Callback یک سطح به سلسله‌مراتب تودرتو اضافه می‌کند. زمانی که Callback-های زیادی وجود داشته باشند، کد به سرعت پیچیده می‌شود:

window.addEventListener('load'،() => {
    document.getElementById('button').addEventListener('click'،() => {
        setTimeout(() => {
                items.forEach(item => { //your code here
                })
            }،
            2000)
    })
})

این کد صرفاً 4 سطح ساده دارد، اما در موارد زیادی سطوح بسیار بیشتری از کد تودرتو را مشاهده می‌کنیم که جالب نیست. برای حل این مشکل چه باید کرد:

جایگزین‌های Callback-ها

از ES6 به بعد جاوا اسکریپت چند قابلیت معرفی کرده است که به کدنویسی ناهمگام بدون استفاده از Callback-ها کمک می‌کند:

  • Promises (ES6)
  • Async/Await (ES8)

Promise-ها

Promise-ها یک روش برای اجرای کد ناهمگام در جاوا اسکریپت محسوب می‌شوند و با استفاده از آن‌ها دیگر لازم نیست نگران وجود Callback-های زیاد در کد باشیم.

مقدمه‌ای بر Promise-ها

یک Promise عموماً به صورت واسطی برای یک مقدار تعریف می‌شود که در زمانی در آینده موجود خواهد شد. با این که سال‌هاست Promise-ها معرفی شده‌اند اما صرفاً در ES2015 استاندارد و معرفی شدند و هم اکنون در ES2017 با معرفی تابع‌های async منسوخ گشته‌اند. تابع‌های async از API مربوط به Promise ها به عنوان بلوک‌های سازنده خود استفاده می‌کند و از این رو درک آن‌ها حتی در صورتی که بخواهید در کد خود از تابع‌های async به جای promise استفاده کنید ضروری خواهد بود.

طرز کار Promise-ها به طور خلاصه

زمانی که یک Promise فراخوانی می‌شود در «حالت انتظار» (pending state) کار خود را آغاز می‌کند. این بدان معنی است که تابع فراخواننده به اجرای خود ادامه می‌دهد در حالی که منتظر Promise است تا پردازش‌های خودش را اجرا کند و بازخوردی به تابع فراخواننده بدهد.

در این زمان تابع فراخواننده منتظر آن می‌ماند تا Promise را در «حالت حل شده» (resolved state) یا در «حالت رد شده» (rejected state) بازگشت دهد، اما چنان که می‌دانید جاوا اسکریپت همگام است و از این رو در زمانی که Promise کار نمی‌کند، تابع همچنان به اجرای خود ادامه می‌دهد.

کدام API جاوا اسکریپت از Promise-ها استفاده می‌کند؟

علاوه بر کد کاربر و کد کتابخانه‌های مختلف، Promise-ها از سوی API-های مدرن استاندارد وب مانند لیست زیر نیز مورد استفاده قرار می‌گیرند:

  • Battery API
  • Fetch API
  • Service Workers

این که در جاوا اسکریپت مدرن از Promise-ها استفاده نکنید امر بسیار نامحتملی است، بنابراین در ادامه به بررسی دقیق‌تر آن‌ها می‌پردازیم.

ایجاد یک Promise

API مربوط به Promise یک سازنده Promise عرضه می‌کند که با استفاده از ()new Promise مقداردهی اولیه می‌شود:

let done = true
----------------------------------------
const isItDoneYet = new Promise((resolve، reject) => {
    if (done) {
        const workDone = 'Here is the thing I built'
        resolve(workDone)
    } else {
        const why = 'Still working on something else'
        reject(why)
    }
})

چنان که می‌بینید Promise ثابت سراسری done را بررسی می‌کند و اگر درست باشد، یک Promise حل شده و در غیر این صورت Promise رد شده بازگشت می‌یابد. ما با استفاده از resolve و reject می‌توانیم یک مقدار را بازگشت دهیم. در حالت فوق ما صرفاً یک رشته بازگشت می‌دهیم اما می‌توان یک شیء را نیز بازگشت داد.

مصرف کردن یک Promise

در بخش قبلی، با روش ایجاد شدن یک Promise آشنا شدیم. اکنون به بررسی شیوه مصرف یا استفاده شدن یک Promise می‌پردازیم:

const isItDoneYet = new Promise(//...)
-----------------------------------------------------------------
const checkIfItsDone = () => {
    isItDoneYet.then((ok) => {
        console.log(ok)
    }).catch((err) => {
        console.error(err)
    })
}

 

جاوا اسکریپت به صورت پیش‌فرض یک زبان برنامه‌نویسی همگام و تک نخی است. این بدان معنی است که کد نمی‌تواند نخ‌های جدیدی ایجاد کرده و به صورت موازی اجرا شود. در این مقاله به بررسی روش‌های مختلف برنامه نویسی ناهمگام در محیط Node.js می‌پردازیم.

ناهمگامی در زبان‌های برنامه‌نویسی

رایانه‌ها اساساً دارای طراحی ناهمگام هستند. منظور از ناهمگامی این است که کارهای مختلف می‌توانند در گردش برنامه اصلی مستقل از هم اتفاق بیفتند. در رایانه‌های امروز هر برنامه‌ای برای مدت زمان معینی اجرا می‌شود و سپس اجرای آن متوقف می‌شود تا برنامه دیگری بتواند به اجرای خود ادامه بدهد. این کار در چنان چرخه سریعی اتفاق می‌افتد که کاربر هرگز متوجه نمی‌شود و ما فکر می‌کنیم رایانه ما می‌تواند به طور همزمان برنامه‌های زیادی را اجرا کند، اما این یک توهم است (البته قضیه در پردازنده‌های چندهسته‌ای متفاوت است).

برنامه‌ها به صورت داخلی از «وقفه» (interrupt) استفاده می‌کنند. وقفه یک سیگنال است که به سمت پردازنده ارسال می‌شود تا توجه سیستم را کسب کند. پرداختن به جزییات این موضوع خارج از موضوع این مقاله است؛ اما باید به خاطر داشته باشید که ناهمگامی در برنامه‌ها امری معمول است و به طور نرمال برنامه‌ها، اجرای خود را متوقف می‌کنند تا زمانی که پردازنده به آن‌ها وقتی اختصاص بدهد. زمانی که یک برنامه منتظر پاسخی از سوی شبکه است، نمی‌تواند پردازنده را معطل خود نگه دارد تا درخواست پایان یابد.

به طور معمول زبان‌های برنامه‌نویسی همگام هستند. برخی از آن‌ها نیز در حد زبان یا کتابخانه‌های جانبی، روش‌هایی برای مدیریت ناهمگامی ارائه می‌کنند. زبان‌های C ،Java ،C# ،PHP ،Go ،Ruby ،Swift و Python همگی به صورت پیش‌فرض همگام هستند. برخی از این زبان‌ها با استفاده از نخ‌ها به صورت ناهمگام عمل می‌کنند و می‌توانند یک پردازش جدید ایجاد کنند.

جاوا اسکریپت

زبان برنامه‌نویسی جاوا اسکریپت نیز به صورت پیشفرض همگام و تک نخی است. این بدان معنی است که کد نمی‌تواند نخ‌های جدید ایجاد کرده و به صورت موازی اجرا شود. در این حالت، خطوط مختلف کد یکی پس از دیگری و به ترتیب اجرا می‌شوند. برای نمونه:

const a = 1const b = 2const c = a * bconsole.log(c)doSomething()

اما می‌دانیم که جاوا اسکریپت درون مرورگر تولد یافته است و وظیفه اصلی آن در ابتدا پاسخ دادن به واکنش‌های کاربر مانند onClick ،onMouseOver ،onChange ،onSubmit و غیره بوده است. چنین فرایندی در یک مدل برنامه‌نویسی همگام چگونه ممکن است؟

پاسخ در محیط برنامه‌نویسی این زبان است. مرورگر روشی برای ارائه یک مجموعه از API-ها دارد که می‌توانند این نوع کارکرد را مدیریت کنند. در سال‌های اخیر Node.js یک محیط غیر مسدودکننده I/O معرفی کرده است که این مفهوم را به دسترسی فایل‌ها، فراخوانی‌های شبکه و موارد مشابه گسترش داده است.

Callback-ها

ما هرگز نمی‌دانیم که یک کاربر چه زمانی روی یک دکمه کلیک خواهد کرد، بنابراین باید یک «دستگیره رویداد» (event handler) برای رویداد کلیک تعریف کنیم.

این دستگیره رویداد تابعی قبول می‌کند که هنگام اتفاق افتادن رویداد فراخوانی می‌شود:

document.getElementById('button').addEventListener('click'، () => {//item clicked})

این همان callback مشهور است. در واقع callback تابع ساده‌ای است که به صورت یک مقدار به تابع دیگر ارسال می‌شوند و تنها زمانی اجرا خواهد شد که رویدادی اتفاق بیفتد. ما این کار را به این دلیل می‌توانیم اجرا کنیم که جاوا اسکریپت تابع‌های درجه نخست دارد که می‌توانند به متغیرها انتساب یافته و به تابع‌های دیگر (که تابع‌های درجه بالا نامیده می‌شوند) ارسال شوند.

رویه معمول این است که کد کلاینت در یک شنونده رویداد load در شیء window قرار می‌گیرد و تابع callback را در زمان آماده شدن صفحه اجرا می‌کند:

	
window.addEventListener('load'، () => {//window loaded //do what you want}

Callback-ها در همه جا استفاده می‌شوند و اختصاص به رویدادهای DOM ندارند. یک مثال عمومی استفاده از تایمر است:

setTimeout(() => {// runs after 2 seconds}، 2000

درخواست‌های XHR نیز یک callback می‌پذیرند. در مثال زیر یک تابع به مشخصه‌ای که در زمان رخداد اتفاق خاص فراخوانی می‌شود، انتساب خواهد یافت:

const xhr = new XMLHttpRequest() xhr.onreadystatechange = () => {
    if (xhr.readyState === 4) {
        xhr.status === 200 ? console.log(xhr.responseText) : console.error('error')
    }
}
xhr.open('GET', 'https://yoursite.com') xhr.send()

مدیریت خطا در Callback-ها

یکی از رایج‌ترین راهبردها برای مدیریت خطا در callback-ها کاری است که Node.js انجام می‌دهد. پارامتر اول در هر تابع callback شیء خطا است که به نام callback-های error-first شناخته می‌شوند.

اگر خطایی وجود نداشته باشد، این شیء null خواهد بود. اگر خطایی رخ داده باشد این پارامتر نوعی توضیح در مورد خطا و اطلاعات مرتبط ارائه می‌کند.

fs.readFile('/file.json'، (err، data) => { if (err!== null) {//handle error console.log(err) return }

مشکل Callback-ها

Callback-ها در استفاده‌های ساده بسیار عالی هستند. اما هر Callback یک سطح به سلسله‌مراتب تودرتو اضافه می‌کند. زمانی که Callback-های زیادی وجود داشته باشند، کد به سرعت پیچیده می‌شود:

window.addEventListener('load'،() => {
    document.getElementById('button').addEventListener('click'،() => {
        setTimeout(() => {
                items.forEach(item => { //your code here
                })
            }،
            2000)
    })
})

این کد صرفاً 4 سطح ساده دارد، اما در موارد زیادی سطوح بسیار بیشتری از کد تودرتو را مشاهده می‌کنیم که جالب نیست. برای حل این مشکل چه باید کرد؟

جایگزین‌های Callback-ها

از ES6 به بعد جاوا اسکریپت چند قابلیت معرفی کرده است که به کدنویسی ناهمگام بدون استفاده از Callback-ها کمک می‌کند:

  • Promises (ES6)
  • Async/Await (ES8)

Promise-ها

Promise-ها یک روش برای اجرای کد ناهمگام در جاوا اسکریپت محسوب می‌شوند و با استفاده از آن‌ها دیگر لازم نیست نگران وجود Callback-های زیاد در کد باشیم.

مقدمه‌ای بر Promise-ها

یک Promise عموماً به صورت واسطی برای یک مقدار تعریف می‌شود که در زمانی در آینده موجود خواهد شد. با این که سال‌هاست Promise-ها معرفی شده‌اند اما صرفاً در ES2015 استاندارد و معرفی شدند و هم اکنون در ES2017 با معرفی تابع‌های async منسوخ گشته‌اند. تابع‌های async از API مربوط به Promise ها به عنوان بلوک‌های سازنده خود استفاده می‌کند و از این رو درک آن‌ها حتی در صورتی که بخواهید در کد خود از تابع‌های async به جای promise استفاده کنید ضروری خواهد بود.

طرز کار Promise-ها به طور خلاصه

زمانی که یک Promise فراخوانی می‌شود در «حالت انتظار» (pending state) کار خود را آغاز می‌کند. این بدان معنی است که تابع فراخواننده به اجرای خود ادامه می‌دهد در حالی که منتظر Promise است تا پردازش‌های خودش را اجرا کند و بازخوردی به تابع فراخواننده بدهد.

در این زمان تابع فراخواننده منتظر آن می‌ماند تا Promise را در «حالت حل شده» (resolved state) یا در «حالت رد شده» (rejected state) بازگشت دهد، اما چنان که می‌دانید جاوا اسکریپت همگام است و از این رو در زمانی که Promise کار نمی‌کند، تابع همچنان به اجرای خود ادامه می‌دهد.

کدام API جاوا اسکریپت از Promise-ها استفاده می‌کند؟

علاوه بر کد کاربر و کد کتابخانه‌های مختلف، Promise-ها از سوی API-های مدرن استاندارد وب مانند لیست زیر نیز مورد استفاده قرار می‌گیرند:

  • Battery API
  • Fetch API
  • Service Workers

این که در جاوا اسکریپت مدرن از Promise-ها استفاده نکنید امر بسیار نامحتملی است، بنابراین در ادامه به بررسی دقیق‌تر آن‌ها می‌پردازیم.

ایجاد یک Promise

API مربوط به Promise یک سازنده Promise عرضه می‌کند که با استفاده از ()new Promise مقداردهی اولیه می‌شود:

let done = true
----------------------------------------
const isItDoneYet = new Promise((resolve، reject) => {
    if (done) {
        const workDone = 'Here is the thing I built'
        resolve(workDone)
    } else {
        const why = 'Still working on something else'
        reject(why)
    }
})

چنان که می‌بینید Promise ثابت سراسری done را بررسی می‌کند و اگر درست باشد، یک Promise حل شده و در غیر این صورت Promise رد شده بازگشت می‌یابد. ما با استفاده از resolve و reject می‌توانیم یک مقدار را بازگشت دهیم. در حالت فوق ما صرفاً یک رشته بازگشت می‌دهیم اما می‌توان یک شیء را نیز بازگشت داد.

مصرف کردن یک Promise

در بخش قبلی، با روش ایجاد شدن یک Promise آشنا شدیم. اکنون به بررسی شیوه مصرف یا استفاده شدن یک Promise می‌پردازیم:

const isItDoneYet = new Promise(//...)
-----------------------------------------------------------------
const checkIfItsDone = () => {
    isItDoneYet.then((ok) => {
        console.log(ok)
    }).catch((err) => {
        console.error(err)
    })
}

اجرای ()checkIfItsDone موجب اجرا شدن یک Promise به نام ()isItDoneYet می‌شود که منتظر حل شدن با استفاده از Callback-ی به نام then باقی می‌ماند و اگر خطایی رخ دهد آن را در callback با نام catch مدیریت می‌کند.

زنجیره‌سازی Promise-ها

هر Promise می‌تواند به Promise دیگری بازگشت یابد و یک زنجیره از Promise-ها تشکیل دهد. مثالی عالی از زنجیره‌سازی Promise-ها در API با نام Fetch مشاهده می‌شود که یک لایه فوقانی روی API مربوط به XMLHttpRequest است و می‌توان از آن برای ایجاد یک منبع و صف‌بندی یک زنجیره از Promise-ها برای اجرا در زمان واکشی شدن منبع استفاده کرد.

API با نام Fetch یک سازوکار مبتنی بر Promise است به طوری که فراخوانی ()fetch معادل تعریف کردن یک Promise با استفاده از ()new Promise است.

نمونه‌ای از زنجیره‌سازی Promise-ها

const status = (response) => {
    if (response.status >= 200 && response.status < 300) {
        return Promise.resolve(response)
    }
    return Promise.reject(new Error(response.statusText))
}
const json = (response) => response.json()
fetch('/todos.json').then(status).then(json).then((data) => {
    console.log('Request succeeded with JSON response'،
        data)
}).catch((error) => {
    console.log('Request failed'،
        error)
})

در این مثال، ()fetch را فراخوانی می‌کنیم تا فهرستی از آیتم‌های TODO را از فایل todos.json که در ریشه دامنه قرار دارد به دست آوریم و بنابراین یک زنجیره از Promise-ها ایجاد کرده‌ایم.

اجرای ()fetch موجب بازگشت یک response می‌شود که مشخصه‌های زیادی دارد، اما دو مورد از آن‌ها برای ما مهم هستند:

  • Status – یک مقدار عددی است که کد وضعیت HTTP را نشان می‌دهد.
  • statusText – یک پیام وضعیت است که در صورت موفق بودن درخواست، مقدار آن OK خواهد بود.

response یک متد ()json نیز دارد که یک Promise بازگشت می‌دهد و در آن به بررسی محتوای متنی پردازش شده و تبدیل آن به JSON می‌پردازد. با توجه به Promise-های فوق روند رویدادها به این صورت است که Promise اول در زنجیره، تابعی است که ما تعریف کرده‌ایم و آن را ()status می‌نامیم. این Promise وضعیت پاسخ را بررسی می‌کند و در صورتی که پاسخ موفق (کد بین 200 تا 299) نباشد، Promise را رد می‌کند.

این عملیات موجب می‌شود که زنجیره Promise همه Promise-های بعدی لیست شده را رد کند و مستقیماً به گزاره ()catch در انتهای کد برسد و متن Request failed را به همراه پیام خطا لاگ کند. اما اگر Promise اول موفق باشد، تابع ()json را که تعریف کرده‌ایم، فراخوانی می‌کند. از آنجا که Promise قبلی در صورت موفقیت، شیء response را بازگشت داده است، می‌توانیم آن را به عنوان ورودی به Promise دوم بدهیم.

در این حالت، داده‌های JSON پردازش شده را بازگشت می‌دهیم تا Promise سوم مستقیماً JSON را دریافت کند:

.then((data) => { console.log('Request succeeded with JSON response'، data)})

در نهایت آن را در کنسول لاگ می‌کنیم.

مدیریت خطاها

در مثال بخش قبلی، یک catch داشتیم که به زنجیره Promise-ها الصاق می‌شد. هر زمان که جزئی از زنجیره Promise-ها ناموفق باشد و خطایی رخ دهد یا یک Promise رد شود، کنترل به گزاره ()catch در انتهای زنجیره می‌رسد.

new Promise((resolve، reject) => {
    throw new Error('Error')
}).catch((err) => {
    console.error(err)
})

یا

new Promise((resolve، reject) => {
    reject('Error')
}).catch((err) => {
    console.error(err)
})

آبشار سازی خطاها

اگر درون ()catch خطایی رخ دهد، می‌توان یک ()catch دیگر برای مدیریت آن الصاق کرد و این روند همین طور تا انتها ادامه می‌یابد:

new Promise((resolve، reject) => {
    throw new Error('Error')
}).catch((err) => {
    throw new Error('Error')
}).catch((err) => {
    console.error(err)
})

هماهنگ‌سازی Promise-ها

در این بخش به روش هماهنگ کردن چند Promise می‌پردازیم.

()Promise.all

اگر لازم است که Promise-های مختلف را با یکدیگر هماهنگ کنید، می‌توانید از متد ()Promise.all برای تعریف کردن لیستی از Promise-ها استفاده کنید و زمانی که همه آن‌ها حل شدند، تابع دیگری را اجرا کنید.

مثالی از آن به صورت زیر است:

const f1 = fetch('/something.json') 
const f2 = fetch('/something2.json')

Promise.all([f1، f2]).then((res) => {
    console.log('Array of results'،
        res)
}).catch((err) => {
    console.error(err)
})

ساختار destructuring assignment در ES2015 نیز اجازه اجرای چنین کاری را می‌دهد:

Promise.all([f1، f2]).then(([res1، res2]) => {
    console.log('Results'،
        res1، res2)
})

البته شما محدود به استفاده از fetch نیستید و می‌توانید از هر Promise دیگری نیز استفاده کنید.

()Promise.race

این متد زمانی اجرا می‌شود که اولین Promise که به آن ارسال کرده‌اید حل شود و callback الصاق شده به آن را یک بار به همراه نتیجه نخستین Promise حل شده اجرا می‌کند. مثالی از آن به صورت زیر است:

const first = new Promise((resolve، reject) => {
    setTimeout(resolve، 500، 'first')
}) const second = new Promise((resolve، reject) => {
    setTimeout(resolve، 100، 'second')
})

Promise.race([first، second]).then((result) => {
            console.log(result) // second}

یک خطای رایج

اگر با خطای زیر در کنسول مواجه شدید:

Uncaught TypeError: undefined is not a promise

ابتدا باید اطمینان حاصل کنید که به جای ()Promise از ()new Promise استفاده کرده‌اید.

Async و Await

جدیدترین رویکرد به تابع‌های ناهمگام در جاوا اسکریپت Async و Await است. جاوا اسکریپت در طی مدت بسیار کوتاهی (ES2015) استفاده از Callback را کنار گذاشت و به بهره‌گیری از Promise-ها روی آورد. از نسخه ES2017 نیز کدهای ناهمگام در جاوا اسکریپت با استفاده از ساختار async/await ساده‌تر شده‌اند.

تابع‌های async ترکیبی از Promise-ها و generatos-ها هستند و اساساً لایه بالاتری از تجرید نسبت به Promise محسوب می‌شوند. در اینجا باید یک بار دیگر تکرار کنیم که async/await بر مبنای Promise-ها ساخته شده‌اند.

Async/Await چرا معرفی شد؟

تابع‌های Async/Await باعث کاهش حجم کد مورد نیاز برای نوشتن Promise-ها می‌شوند و محدودیت «عدم قطع زنجیره» Promise-های زنجیره‌سازی را نیز مرتفع می‌سازند. زمانی که Promise-ها در نسخه ES2015 معرفی شدند، هدف از ارائه آن‌ها حل مشکلی در کدنویسی ناهمگام بود و در این کار نیز موفق بودند، اما در طی دو سال که بین عرضه نسخه‌های ES2015 و ES2017 وجود داشت مشخص شد که Promise-ها نمی‌توانند راه‌حل نهایی باشند.

Promise-ها برای حل مشکل مشهور جهنم callback-ها عرضه شدند، اما آن‌ها نیز پیچیدگی خاص خود را داشتند و از نظر کاربران دارای ساختار پیچیده‌ای بودند. Promise-ها با این کارکرد که به ما نشان دادند می‌توان از ساختار مناسب‌تری به این منظور استفاده کرد مفید بودند و لذا اینک زمانی فرا رسیده است که از تابع‌های async استفاده کنیم. تابع‌های async/await موجب می‌شوند کد همگام به نظر برسد، اما اساساً ناهمگام است و در پشت صحنه به روشی غیر مسدودکننده عمل می‌کند.

طرز کار Async/Await چگونه است؟

تابع async یک Promise بازگشت می‌دهد. به مثال زیر توجه کنید:

const doSomethingAsync = () => {
    return new Promise((resolve) => {
        setTimeout(() => resolve('I did something')، 3000)
    })
}

زمانی که بخواهیم این تابع را فراخوانی کنیم یک await آماده می‌کنیم و کد فراخوانی کننده متوقف می‌شود تا این که Promise حل یا رد شود. تنها الزام این است که تابع کلاینت باید به صورت async تعریف شود. به مثال زیر توجه کنید:

const doSomething = async () => {
    console.log(await doSomethingAsync())
}

یک مثال ساده

در مثال ساده زیر از async/await برای اجرای ناهمگام یک تابع استفاده شده است:

const doSomethingAsync = () => {
    return new Promise((resolve) => {
        setTimeout(() => resolve('I did something')، 3000)
    })
}
const doSomething = async () => {
    console.log(await doSomethingAsync())
}
console.log('Before') 
doSomething() 
console.log('After')

کد فوق عبارت زیر را در کنسول مرورگر نمایش می‌دهد:

BeforeAfterI did something //after 3s

Promise کردن همه چیز

الصاق کردن کلیدواژه async به هر تابعی به این معنی است که تابع یک Promise بازگشت خواهد داد. حتی اگر این کار به طور صریح انجام نشده باشد، این کار موجب می‌شود که تابع به صورت درونی یک Promise بازگشت دهد. به همین دلیل است که کد زیر معتبر است:

const aFunction = async () => { return 'test'}
aFunction().then(alert) // This will alert 'test'

این کد نیز مانند کد فوق است:

const aFunction = async () => { return Promise.resolve('test')}
aFunction().then(alert) // This will alert 'test

خواندن این کد کاملاً آسان‌تر است

چنان که در مثال فوق می‌بینیم، کد ما بسیار ساده به نظر می‌رسد. آن را با کدی که از Promise-های ساده استفاده می‌کرد و از زنجیره‌سازی و تابع‌های callback بهره می‌گرفت مقایسه کنید. این یک مثال بسیار ساده است، مزیت اصلی زمانی بروز می‌یابد که کد بسیار پیچیده‌تر باشد. برای نمونه با استفاده از Promise-ها به صورت زیر می‌توان یک منبع JSON را دریافت کرده و آن را تحلیل کرد:

const getFirstUserData = () => {
    return fetch('/users.json')
    get users list.then(response => response.json())
    parse JSON.then(users => users[0])
    pick first user.then(user => fetch(`/users/${user.name}`)) 
    get user data.then(userResponse => response.json()) parse JSON
}

getFirstUserData()

کد زیر همان کارکرد فوق را با استفاده از await/async اجرا می‌کند:

const getFirstUserData = async () => {
    const response = await fetch('/users.json') get users list
    const users = await response.json() parse JSON
    const user = users[0] pick first user
    const userResponse = await fetch(`/users/${user.name}`) get user data
    const userData = await user.json() parse JSON
    return userData
}

getFirstUserData()

سری کردن چند تابع Async

تابع‌های Async را می‌توان بسیار ساده به هم زنجیر کرد و ساختار آن نسبت به Promise-های ساده بسیار خواناتر است:

const promiseToDoSomething = () => {
    return new Promise(resolve => {
        setTimeout(() => resolve('I did something'), 10000)
    })
}

const watchOverSomeoneDoingSomething = async () => {
    const something = await promiseToDoSomething() 
    return something + ' and I watched'
}

const watchOverSomeoneWatchingSomeoneDoingSomething = async () => {
    const something = await watchOverSomeoneDoingSomething()
    return something + ' and I watched as well'
}
watchOverSomeoneWatchingSomeoneDoingSomething().then((res) => { console.log(res)})

کد فوق عبارت زیر را نمایش می‌دهد:

I did something and I watched and I watched as well

دیباگ آسان‌تر

دیباگ کردن Promise-ها دشوار است زیرا دیباگر روی کد ناهمگام نمی‌ایستد. اما async/await کار دیباگ را بسیار آسان‌تر ساخته‌اند، زیرا از نظر کامپایلر آن‌ها کد همگام هستند.

Event Emitter در Node.js

با استفاده از Event Emitter در Node.js می‌توان رویدادهای سفارشی ایجاد کرد. اگر با جاوا اسکریپت در مرورگر کار کرده باشید، می‌دانید که بخش عمده تعامل با کاربر از طریق رویدادهایی مانند کلیک‌های ماوس، فشردن کلیدهای کیبورد، واکنش به حرکت ماوس و غیره مدیریت می‌شود. در سمت بک‌اند، Node.js کلاس EventEmitter را ارائه کرده است که برای مدیریت رویدادها استفاده می‌شود. این کلاس را می‌توان با استفاده از کد زیر مقداردهی کرد:

const eventEmitter = require('events').EventEmitter()

این شیء متدهای on و emit برخی متدهای دیگر را عرضه می‌کند.

  • emit – برای تحریک کردن یک رویداد استفاده می‌شود.
  • on – برای افزودن تابع callback استفاده می‌شود که قرار است هنگام تحریک شدن رویداد اجرا شود.

برای نمونه، یک رویداد start ایجاد می‌کنیم و از آنجا که می‌خواهیم مثال ساده‌ای باشد، واکنش ما به آن لاگ کردن پیامی در کنسول است:

eventEmitter.on('start'، () => { console.log('started')})

زمانی که رویداد را اجرا کنیم:

eventEmitter.emit('start')

تابع دستگیره رویداد تحریک می‌شود و لاگ کنسول ایجاد می‌شود. با استفاده از آرگومان‌های اضافی ()emit می‌توان آرگومان‌هایی به دستگیره رویداد ارسال کرد:

eventEmitter.emit('start')
eventEmitter.emit('start'، 23)

آرگومان‌های چندگانه

eventEmitter.on('start'، (start، end) => { console.log(`started from ${start} to ${end}`)})
eventEmitter.emit('start'، 1، 100)

شیء EventEmitter چند متد دیگر نیز برای تعامل با رویدادها در اختیار ما قرار می‌دهد که از آن جمله‌اند:

  • ()once – یک شنونده یک‌بارمصرف اضافه می‌کند.
  • ()removeListener() / off – یک شنونده رویداد را از رویداد حذف می‌کند.
  • ()removeAllListeners – همه شنونده‌ها را از یک رویداد حذف می‌کند.

 

منبع: وبلاگ فرادرس

تاریخ انتشار: چهارشنبه, ۳۱ ارديبهشت ۱۳۹۹، ۰۷:۵۱ ق.ظ نویسنده: محمدیان

به نام خدا

آبجکت global و کار با کنسول:

علاوه بر دستور console.log دستورات دیگری هم هستند که دانستن آن منجر به کدنویسی حرفه ای و بهینه تر مییشود.

مثلا همین دستور console.log را درنظر بگیرید و کد زیر را خودتان بررسی نمائید.

var person1 = {name:"Mokhtar",age:34,favorite:"javascript"};
var person2 = {name:"Afshin",age:42,favorite:"phone"};
var person3 = {name:"Amirhosien",age:12,favorite:"game"};

console.log(person1)
console.log(person2)
console.log(person3)

// ------------------  نتیجه  -------------------------------
{ name: 'Mokhtar', age: 34, favorite: 'javascript' }
{ name: 'Afshin', age: 42, favorite: 'phone' }
{ name: 'Amirhosien', age: 12, favorite: 'game' }

حالا میخواهیم به شکل کوتاهتر و کامل تری به همین نتیجه + نام متغیرهای مربوط آبجکت ها دست پیدا کنیم. بنابراین از دستور تک خطی زیر استفاده میکنیم:

console.log({person1,person2,person3})

// ------------------  نتیجه  -------------------------------
{ person1: { name: 'Mokhtar', age: 34, favorite: 'javascript' },
  person2: { name: 'Afshin', age: 42, favorite: 'phone' },
  person3: { name: 'Amirhosien', age: 12, favorite: 'game' } }

با روش بعدی میتوانیم اطلاعات فوق را به صورت یک جدول گرافیکی دریافت کنیم. روش بعدی استفاده از دستور console.table است.

     1- در حالت اول، متغیرها را درون آرایه[ ] قرار میدهیم و به متود table پاس میدهیم:

console.table([person1,person2,person3]);

// ------------------  نتیجه  -------------------------------
┌─────────┬──────────────┬─────┬──────────────┐
│ (index) │     name     │ age │   favorite   │
├─────────┼──────────────┼─────┼──────────────┤
│    0    │  'Mokhtar'   │ 34  │ 'javascript' │
│    1    │   'Afshin'   │ 42  │   'phone'    │
│    2    │ 'Amirhosien' │ 12  │    'game'    │
└─────────┴──────────────┴─────┴──────────────┘

    2- در حالت دوم، متغیرها را درون یک آبجکت { } قرار میدهیم و به متود table پاس میدهیم:

console.table({person1,person2,person3});

// ------------------ نتیجه -------------------------------
┌─────────┬──────────────┬─────┬──────────────┐
│ (index) │     name     │ age │   favorite   │
├─────────┼──────────────┼─────┼──────────────┤
│ person1 │  'Mokhtar'   │ 34  │ 'javascript' │
│ person2 │   'Afshin'   │ 42  │   'phone'    │
│ person3 │ 'Amirhosien' │ 12  │    'game'    │
└─────────┴──────────────┴─────┴──────────────┘

فرقش هم که متوجه شدید. 

وقتی با آرایه جدول میسازیم، شناسه هر ردیف همان ایندکس آرایه است و وقتی هم که با آبجکت جدول میسازیم، شناسه هر ردیف نام متغیری است که آبجکت در آن ذخیره شده است.

 

نکته: موارد بالا فقط نمونه بودند، مقدار و نوع هر متغیر میتواند متفاوت باشد. اما در هر صورت جدول ساخته میشود. 

 

 

اگر خواستیم بدونیم که مثلاً سرعت اجرای برنامه ما یا یک تابعی که نوشتیم چقدره. چکار کنیم؟ 

از دستور console.time برای این هدف میتونیم استفاده کنیم. یعنی در جایی از کُد که میخوایم محاسبه زمان شروع بشه دستور console.time رو استفاده میکنیم و یک نام دلخواه مثلا timer  رو به عنوان متغیری که زمان رو داخل خودش نگه میداره به عنوان پارامتر به تابع time میدیم. و

console.time('timer');
var person1 = {name:"Mokhtar",age:34,favorite:"javascript"};
var person2 = {name:"Afshin",age:42,favorite:"phone"};
var person3 = {name:"Amirhosien",age:12,favorite:"game"};

console.table({person1,person2,person3})
console.timeEnd('timer')

// ------------------ نتیجه -------------------------------
┌─────────┬──────────────┬─────┬──────────────┐
│ (index) │     name     │ age │   favorite   │
├─────────┼──────────────┼─────┼──────────────┤
│ person1 │  'Mokhtar'   │ 34  │ 'javascript' │
│ person2 │   'Afshin'   │ 42  │   'phone'    │
│ person3 │ 'Amirhosien' │ 12  │    'game'    │
└─────────┴──────────────┴─────┴──────────────┘
timer: 14.346ms

 

بنابراین توابع فوق موقع دیباگ کردن پروژه میتونن کارگشا باشند. البته یه تابع دیگه هم هست به نام console.trace که اطلاعات خوبی رو میتونه واسمون لاگ کنه. مثلا میخواهیم بدونیم که تابع ما در چه خطوطی در برنامه اجرا شده چه ماژول هایی باعث اجرای اون شدن و ... 

در مثال زیر من نام trace رو xxxx گذاشتم. هر نام دیگه ای میتونیم بزاریم تا وقتی تعداد لاگ ها زیاد شد بفهمیم مربوط به کدوم trace هست.

const mok = ()=>{
    let mokname = "mokhtar";
    console.log(mokname);
    console.trace('xxxx');
}

mok()

// ------------------ نتیجه -------------------------------
Trace: xxxx
    at mok (C:\Users\Mohammadiyan\Desktop\mynode\index.js:9:13)
    at Object.<anonymous> (C:\Users\Mohammadiyan\Desktop\mynode\index.js:13:1)
    at Module._compile (internal/modules/cjs/loader.js:778:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
    at Module.load (internal/modules/cjs/loader.js:653:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
    at Function.Module._load (internal/modules/cjs/loader.js:585:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3)

در ضمن با دستور console.clear میتونیم صفحه کنسول رو پاک کنیم.

console.clear()

console.count و console.countReset هم توابع کارآمدی هستند در زمان خودش. console.count اگر بدون پارامتر اجرا شه، خودش رو در هر بار اجرا میشماره. ولی درسته که خودش رو میشماره اما اگر ما اون رو درون یک تابع به کار ببریم و یک متغیر رشته ای یا اصلا نام همون تابع خودمون رو بهش بدیم با اینکه باز هم داره خودش رو میشماره ولی ما کاری کردیم که نتیجه ای که در آخر روی کنسول چاپ میشه، تعداد اجرای تابع خودمون باشه. خیلی جاها میشه ازش استفاده کرد ولی من مثال زیر رو تمرین کردم:

const mok = ()=>{
    let funcName = "mok";
    console.count(funcName);
}

mok();
mok();
mok();

// ------------------ نتیجه -------------------------------
mok: 1
mok: 2
mok: 3

هر کجا هم که از console.countReset استفاده کنیم. مقدار شمارش 0 میشه. از اسمشم معلومه.

 

 

یه دستوری که به نظر من خیلی کاربردی هست و خودم هم تا الان با اینکه بهش احتیاج داشتم،ولی ازش خبر نداشتم دستور console.dir هست. 

این دستور شبیه دستور dir در سیستم عامل هست که لیست دایرکتوری های موجود رو بهمون میده اما در کنسول جاوااسکریپت ما یه پارامتر به تابع dir میدیم که معمولا یک آبجکت یا آرایه هست و خروجی ما لیستی از خواص و توابع یا متودهای قابل استفاده از اون شی یا اشیاست. 

توی صفحه مرور کلی جاوااسکریپت گفتم که همه چیز در جاوااسکریپت شی هستند و کلا جاوااسکریپت بر پایه شی گرایی نوشته شده. حتی خود دستور console هم یک شی هست. بنابراین میتونیم خود console رو به عنوان پارامتر به دستور console.dir بدیم تا لیستی از خواص و متودهای قابل استفاده از شی کنسول رو به ما بده:

console.dir(console);

// ------------------ نتیجه -------------------------------
Console {
  log: [Function: bound consoleCall],
  debug: [Function: bound consoleCall],
  info: [Function: bound consoleCall],
  dirxml: [Function: bound consoleCall],
  warn: [Function: bound consoleCall],
  error: [Function: bound consoleCall],
  dir: [Function: bound consoleCall],
  time: [Function: bound consoleCall],
  timeEnd: [Function: bound consoleCall],
  timeLog: [Function: bound timeLog],
  trace: [Function: bound consoleCall],
  assert: [Function: bound consoleCall],
  clear: [Function: bound consoleCall],
  count: [Function: bound consoleCall],
  countReset: [Function: bound consoleCall],
  group: [Function: bound consoleCall],
  groupCollapsed: [Function: bound consoleCall],
  groupEnd: [Function: bound consoleCall],
  table: [Function: bound consoleCall],
  Console: [Function: Console],
  markTimeline: [Function: markTimeline],
  profile: [Function: profile],
  profileEnd: [Function: profileEnd],
  timeline: [Function: timeline],
  timelineEnd: [Function: timelineEnd],
  timeStamp: [Function: timeStamp],
  context: [Function: context],
  [Symbol(counts)]: Map {},
  [Symbol(kColorMode)]: 'auto' }

تعدادی از دستورات فوق رو توی همین صفحه باهاش آشنا شدیم. توابع warn و error و info برای خوانایی بهتر اون چیزی لاگ میکنیم استفاده میشه. در کنسول مروگر و کنسول نود جی اس نتایج متفاوتی خواهید دید. یک متن رو به این توابع به عنوان پارامتر بدید و خروجی اونها رو بررسی کنید. (هم در کنسول مرورگر و هم در کنسول نود)

 

با دستور console.group میتونیم لاگ های خودمون رو گروه بندی کنیم،و حتی بهشون برچسب بدیم تا مواردی که در کنسول چاپ میشه منظم تر و قابل شناسایی و خواناتر باشن:

console.group('primary group')
console.log('This is primary log for primary group.');
console.group('secondary group')
console.log('This is secondary log for secondary group.');
console.group('third group')
console.log('This is third log for third group.');

// ------------------ نتیجه -------------------------------
primary group
  This is primary log for primary group.
  secondary group
    This is secondary log for secondary group.
    third group
      This is third log for third group.

در کنسول مرورگر، هر گروه یه کلیپس داره و میتونید گروه را باز و بسته کنید.

 

NodeMon

اگر با nodejs کار میکنید، مطالعه این قسمت براتون مفیده. حتی اگر با nodemon آشنایی دارید و میدونید که پکیج nodemon برای ریلود مجدد برنامه، پس هر تغییر در فایل های برنامه است. باز هم این قسمت برای شما مفیده.

وقتی از این پکیج در پروژه خودمون استفاده میکنیم. صفحه کنسول ما بعد از هر اجرا، شلوغ و شلوغ تر میشه. حتی واسه من پیش اومده که خطای دید باعث اتلاف وقتم شده. خوب حالا چکار کنیم؟

پکیج nodemon، فایلی به نام nodemon.json رو در همون مسیری که فایل package.json قرار داره میشناسه و ما میتونیم تنظیمات خودمون رو درون اون فایل قرار بدیم تا nodemon به شکل دلخواه ما فعالیت کنه. 

در مثال زیر من فقط میخوام در هر بار اجرای nodemon، صفحه کنسول من تمیز بشه و لاگ های قبلی رو نشون نده. بنابراین داخل فایل nodemon.json که از قبل ایجاد کردیم. دستورات زیر رو قرار میدیم.

{
    "events":{
        "start": "cls || clear"
    }
}

با این تنظیمات گفتیم که در رویداد شروع، اول از همه از دستور cls استفاده کن یا اگر cls کار نکرد از دستور clear برای پاک کردن محتوای قبلی استفاده کن. 

برای مطالعه بیشتر میتونید به راهنمای تنظیمات nodemon مراجعه کنید.

 

ادامه دارد ...