حافظه میانگیر (Buffer)، در لغت به معنای حائل یا میانگر میباشد که در علوم مختلف به شکلی متفاوت به مفهوم یک حافظهٔ میانجی یا موقت بکار میرود.
بافر یا buffer عمومأ در علوم کامپیوتر و الکترونیک بیشتر به کار میرود که معمولأ اشاره به حافظه موقت یک سیستم دارد.
مثال:
buffer size = 1Mb به معنی این است که حافظه موقت این سیستم برابر با ۱ مگابایت میباشد یا: اندازه حافظه موقت = 1Mb
مثلاً در شیمی محلول بافر محلولی است که ph را ثابت نگه میدارد، بنابراین به کمک این محلول میتوان در مراحل گوناگون یک فرایند شمیایی ph را ثابت نگه داشته و آن را اندازهگیری نماییم. مدارهای بافر نیز در الکترونیک مفهومی نزدیک به این دارند. به این صورت که دادههای سیگنال دریافتی را در خود نگهداری نموده و بر حسب نیاز به سیستم بعدی تحویل میدهند. مدارهای بافر بهطور کلی به دو دسته آنالوگ و دیجیتال تقسیم میشوند.
معرفی بافرها
بافر به فضایی از حافظه اشاره دارد که از آن برای ذخیرهسازی موقت دادهها استفاده میشود. یک بافر به طور معمول، بین دستگاههایی که از لحاظ سرعت با یکدیگر هماهنگ نیستند، مورد استفاده قرار میگیرد، به طوری که این بافرها بدون اینکه به دادهها آسیب برسانند، میتوانند عملیات در حال اجرا را در یک سرعت به خصوص نگهدارند. از بافرها در node.js، زمانی استفاده میشود که با یک جریان فایل (دقت کنید منظور از جریان یا stream، دنباله ایی متوالی از دادهها است) یا جریان tcp که عمدتاً دارای دادههای باینری (binary) یا هشتتایی (octets) هستند، روبرو میشویم.
برای استفاده از بافرها نیاز به وارد کردن ماژول خاصی نیست، یک کللاس عمومی از آبجکت global است که در همه جای برنامه میتوانید از متودهای آن استفاده کنید.
برخی از متودهای مربوط به کار بافرها
Buffer.alloc(size[, fill[, encoding]]) Buffer.allocUnsafe(size) Buffer.from(string[, encoding]) buf.compare(target[, targetStart[, targetEnd[, sourceStart[, sourceEnd]]]]) Buffer.concat(list) buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]]) buf.equals(otherBuffer) buf.fill(value) buf.indexOf(value) buf.length buf.slice([start[, end]]) ()buf.toJSON buf.toString([encoding[, start[, end]]])
آموزش جریان داده یا Streams در Node.js : کار با Filestream و Pipes در Node.js
Filestream در Node.js
Node به عنوان مکانیسم انتقال داده از جریانهای گسترده استفاده می کند. به عنوان مثال ، هنگامی که شما با استفاده از تابع console.log ، چیزی را به کنسول منتقل می کنید ، در واقع از یک جریان برای ارسال اطلاعات به کنسول استفاده می کنید. Node.js همچنین قابلیت جابجایی داده ها از پرونده ها را دارد تا بتوان آنها را به طور مناسب خواند و نوشت. اکنون به نمونه ای از چگونگی استفاده از جریان ها برای خواندن و نوشتن از پرونده ها خواهیم پرداخت. برای این مثال باید مراحل زیر را دنبال کنیم.
مرحله 1) یک پرونده با نام data.txt ایجاد کنید که داده های زیر را دارد. بگذارید فرض کنیم این پرونده در درایو D دستگاه محلی ما ذخیره شده است.
- آموزش در Node.js
- مقدمه
- مناسبت ها
- مولدها
- اتصال داده
- استفاده از جاسمین
مرحله 2) کد مربوطه را بنویسید که از خواندن داده ها از پرونده استفاده می کند.
var fs = require("fs"); var stream; stream = fs.createReadStream("D://data.txt"); stream.on("data", function(data) { var chunk = data.toString(); console.log(chunk); });
1.ما ابتدا ماژول های ‘fs’ را نیاز داریم که که شامل تمام عملکردهای مورد نیاز برای ایجاد جریان ها است.توضیح کد:
2.در مرحله بعدی ما با استفاده از متد createReadStream ، یک جریان قابل خواندن ایجاد می کنیم . به عنوان ورودی ، ما محل فایل data.txt خود را می دهیم.
3.تابع steam.on یک کنترل کننده رویداد است و در آن ، ما اولین پارامتر را به عنوان “داده” مشخص می کنیم. این بدان معناست که هر زمان داده ها از پرونده وارد جریان می شوند ،یک تابع برگشتی را اجرا می کنند. در این مورد ، ما در حال تعریف یک تابع برگشتی هستیم که 2 مرحله اساسی را انجام خواهد داد. اولین مورد ، تبدیل داده های خوانده شده از پرونده به عنوان رشته است. مورد دوم ارسال رشته تبدیل شده به عنوان خروجی به کنسول است.
4.ما هر قطعه از داده ها را که از جریان داده خوانده می شود ، گرفته و آن را به یک رشته تبدیل می کنیم.
5.سرانجام ، ما خروجی هر قطعه تبدیل شده به رشته را به کنسول ارسال می کنیم.
خروجی:
- در صورت اجرای صحیح کد ، خروجی فوق را در کنسول مشاهده خواهید کرد. این خروجی همان پرونده موجود در پرونده data.txt خواهد بود.
نوشتن فایل بصورت stream در نود جی اس
به همین روش ، که ما یک جریان فایل بصورت Stream ایجاد می کنیم ، می توانیم یک جریان نوشتن فایل بصورت Stream نیز در یک پرونده ایجاد کنیم. بیایید ابتدا یک پرونده خالی و بدون محتوا به نام data.txt ایجاد کنیم. فرض کنیم این پرونده در درایو D کامپیوتر ما قرار گرفته است. کد زیر نشان می دهد که چگونه می توانیم داده را به پرونده بنویسیم.
var fs = require("fs"); var stream; stream = fs.createWriteStream("D://data.txt"); stream.write("Tutorial on Node.js") stream.write("Introduction") stream.write("Events") stream.write("Generators") stream.write("Data Connectivity") stream.write("Using Jasmine")
توضیح کد:
- ما در حال ایجاد یک جریان قابل نوشتار با استفاده از روش – createWriteStream هستیم. به عنوان ورودی ، ما محل فایل txt خود را می دهیم.
- در مرحله بعدی از متد stream.write برای نوشتن سطرهای مختلف متن به فایل متنی استفاده کردیم. stream، از نوشتن این داده ها به پرونده data.txt مراقبت میکند.
اگر پرونده data.txt را باز کنید ، اکنون داده های زیر را در پرونده مشاهده خواهید کرد.
- آموزش در Node.js
- مقدمه
- رویدادها
- مولدها
- اتصال داده
- استفاده از جاسمین
pipes در Node.js
در برنامه های Node ، می توان جریانها را با استفاده از روش ()pipe که دو آرگومان را میگیرد به یکدیگر وصل کرد.
- یک جریان قابل نوشتار ضروری که به عنوان مقصد برای داده ها عمل می کند و
- یک شیء اختیاری که برای انتقال گزینه ها استفاده می شود.
اگر می خواهید داده ها را از یک پرونده به پرونده دیگر انتقال دهید ، نمونه بارز استفاده از pipes است. بنابراین بیایید نمونه ای از چگونگی انتقال داده ها از یک پرونده به پرونده دیگر با استفاده از لوله ها را ببینیم.
مرحله 1) یک پرونده با نام datainput.txt ایجاد کنید که داده های زیر را دارد. بگذارید فرض کنیم این پرونده در درایو D دستگاه محلی ما ذخیره شده است.
- آموزش در Node.js
- مقدمه
- رویدادها
- مولدها
- اتصال داده
- استفاده از جاسمین
مرحله 2) یک پرونده خالی به نام dataOutput.txt ایجاد کنید و آن را در درایو D دستگاه محلی خود قرار دهید.
مرحله 3) برای انجام انتقال داده از پرونده datainput.txt به پرونده dataOutput.txt کد زیر را بنویسید.
var fs = require("fs"); var readStream = fs.createReadStream("D://datainput.txt"); var writeStream = fs.createWriteStream("D://dataOutput.txt"); readStream.pipe(writeStream);
توضیح کد:
- ما ابتدا یک پرونده “readstream” را در پرونده txt خود ایجاد می کنیم که شامل تمام داده های مورد نیاز برای انتقال به پرونده جدید است.
- سپس ما نیاز به ایجاد “Writestream” به پرونده dataOutput.txt که فایل خالی ما است و مقصد انتقال داده ها از پرونده datainput.txt است.
- سپس از دستور pipes برای انتقال اطلاعات از جریان خواندن به جریان نوشتن استفاده می کنیم. دستور pipes ، تمام داده های وارد شده به خواندن را می گیرد ، و آن را به سمت نوشتن متن سوق می دهد.
اگر اکنون پرونده dataOutput.txt را باز کرده اید ، تمام داده های موجود در پرونده datainput.txt را مشاهده خواهید کرد.
رویدادها یا Events در Node.js
Events یا رویدادها یکی از مفاهیم کلیدی در Node.js هستند و بعضی اوقات به Node.js به عنوان یک چارچوب رویداد محور گفته می شود. اصولاً یک رویداد اتفاقی است که رخ می دهد. به عنوان مثال ، اگر یک اتصال به یک پایگاه داده برقرار شود ، رویداد ارتباط بانک اطلاعاتی آغاز می شود. برنامه نویسی رویداد محور، ایجاد توابعی است که باعث شروع رویدادهای خاص می شوند.
بیایید به یک مثال ساده برای تعریف یک رویداد در Node.js بزنیم .ما می خواهیم رویدادی به نام ‘data_received’ ایجاد کنیم. با شروع این رویداد ، متن ” “data received” ” به کنسول ارسال می شود.
var events = require('events'); var eventEmitter = new events.EventEmitter(); eventEmitter.on('data_received', function() { console.log('data received succesfully.'); }); eventEmitter.emit('data_received');
توضیح کد:
- با استفاده از تابع require ، ماژول ” events ” را درج کنید. با استفاده از این ماژول ، می توانید رویدادهایی را در nod .js ایجاد کنید.
- یک فرستنده ی رویدادهای جدید ایجاد کنید. این مورد برای پیوند دادن این رویداد استفاده می شود ، که در این مورد “data_received” به یک تابع برگشتی است که در مرحله 3 تعریف شده است.
- ما یک تابع رویداد محور را تعریف می کنیم که می گوید اگر در صورت بروز رویداد “data_received” ما باید متن “data_received” را درخروجی کنسول قراردهیم.
- در آخر ، ما با استفاده از تابع eventEmiter.emit یک اسلحه دستی از رویداد خود داریم. این رویداد data_received را شروع می کند.
هنگام اجرای برنامه ، متن “data received ” مطابق شکل زیر به کنسول ارسال می شود.
انتشار رویدادها یا Emitting Events در Node.js
هنگام تعریف رویدادها ، روشهای مختلفی برای وقایع وجود دارد که می توان از آنها استفاده کرد. این مبحث تمرکزش را بر جزئیات هر یک از آنها می پردازد.
- یک بار اجرا کننده رویداد
بعضی اوقات ممکن است شما علاقه مند باشید فقط در اولین باری که رویداد اتفاق می افتد واکنش نشان دهید. در این مواقع می توانید از روش ()once استفاده کنید. بیایید ببینیم که چگونه می توانیم از متد once برای اجرای کننده ی رویداد استفاده کنیم.
توضیح کد:
- در اینجا ما از روش “once” استفاده می کنیم تا بگوییم که برای رویداد “data_received” ، تابع برگشتی فقط باید یک بار انجام شود.
- در اینجا ما به طور دستی رویداد “data_received” را شروع می کنیم.
- وقتی رویداد “data_received” دوباره شروع شود ، این بار هیچ اتفاقی نخواهد افتاد. این به دلیل اولین قدم است که در آنجا گفتیم که این رویداد فقط یک بار می تواند آغاز شود.
اگر کد به درستی اجرا شود ، خروجی موجود در کنسول با موفقیت “data_received” خواهد بود. این پیام فقط یک بار در کنسول ظاهر می شود.
- بازرسی شنوندگان رویداد
در هر نقطه از عمر خود ، یک پخش کننده رویداد می تواند صفر یا بیشتر شنوندگان داشته باشد که به آن متصل هستند. شنوندگان برای هر نوع رویداد می توانند از چند طریق مورد بازرسی قرار گیرند. اگر شما فقط می خواهید تعداد شنوندگان پیوست را تعیین کنید ، به دنبال روش () EventEmitter.listenerCount نباشید.
(توجه: شنوندگان مهم هستند زیرا برنامه اصلی باید بداندکه آیا شنوندگان در پرواز به یک رویداد اضافه می شوند ، در غیر این صورت این برنامه با نقص کار می کند زیرا شنوندگان اضافی فراخوانی می شوند.)
توضیح کد:
- ما در حال تعریف یک نوع eventEmitter هستیم که برای استفاده از روش های مرتبط با رویداد لازم است.
- سپس ما در حال تعریف شیئی به نام emitter هستیم که برای تعریف کننده های رویداد ما استفاده می شود.
- ما در حال ایجاد 2 رویداد هستیم که اساساً هیچ کاری انجام نمی دهند. این فقط به عنوان مثال ساده برای نشان دادن نحوه عملکرد روش listenerCount می باشد.
- حال هنگامی که از رویداد listenerCount در رویداد data_received ما استفاده می کنید ، تعداد شنوندگان این رویداد را در پرونده کنسول ارسال می کنید.
- اگر کد به درستی اجرا شود ، مقدار 2 در کنسول نشان داده می شود.
3. رویداد newListener
هر بار که یک کنترل کننده رویداد جدید ثبت شود ، پخش کننده رویداد، یک رویداد newListener را منتشر می کند. این رویداد برای شناسایی مجریان رویداد جدید استفاده می شود. شما معمولاً هنگام تخصیص منابع یا انجام برخی اقدامات برای هر اداره کننده رویداد جدید ، معمولاً از رویداد newListener استفاده می کنید.
var events = require('events'); var eventEmitter = events.EventEmitter; var emitter = new eventEmitter(); emitter.on("newListener", function(eventName, listener) { console.log("Added listener for " + eventName + " events"); }); emitter.on('data_received', function() {}); emitter.on('data_received', function() {});
توضیح کد:
- ما در حال ایجاد یک کنترل کننده ی رویداد جدید برای رویداد ‘newListener’ هستیم. بنابراین هر زمان که یک کنترل کننده رویداد جدید ثبت شود ، متن ” “Added listener for” + نام رویداد در کنسول نمایش داده می شود.
- در اینجا ما در حال نوشتن متن کنسول “Added listener for ” + نام رویداد برای هر رویداد ثبت شده هستیم.
- ما در حال تعریف 2 عامل کنترل کننده رویداد برای رویداد “data_received” هستیم.
اگر کد بالا به درستی اجرا شود ، متن زیر در کنسول نشان داده می شود. فقط نشان می دهد که کنترل کننده رویداد “newListener” دو بار شروع شده است.
شنوندگان برای رویدادهای data_received اضافه شدند.
شنوندگان برای رویدادهای data_received اضافه شدند.
خلاصه
- جریان داده در Node.js برای خواندن و نوشتن داده ها از دستگاه های ورودی-خروجی استفاده می شود. Node.js از کتابخانه ‘fs’ برای ایجاد جریانهای قابل خواندن و نوشتاری در پرونده ها استفاده می کند. از این جریان ها می توان برای خواندن و نوشتن داده ها از پرونده ها استفاده کرد.
- برای اتصال چندین جریان به هم می توان از pipes استفاده کرد. یکی از متداول ترین نمونه ها ، انتقال جریان خواندن و نوشتن به یکدیگر برای انتقال داده ها از یک پرونده به پرونده دیگر است.
- Node.js اغلب به عنوان یک چارچوب رویداد محور نیز نشان داده می شود ، و تعریف رویدادها در Node.js. بسیار آسان است. توابع قابل تعریف هستند که به این رویدادها پاسخ می دهند.
- رویدادها همچنین روش هایی برای پاسخ به رویدادهای کلیدی را در معرض نمایش قرار می دهند. به عنوان مثال ، ما شاهد کنترل رویداد () once هستیم که می تواند مورد استفاده قرار گیرد تا اطمینان حاصل شود که یک تابع برگشتی فقط یک بار در هنگام شروع یک رویداد انجام می شود.
خواندن فایل ها با استفاده از ماژول 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
را به عنوان فایل ماژول در نظر می گیرد.
منبع: نتران
به دلیل اینکه با مباحث مهمی روبرو هستیم، سعی کردم یه منبع پیدا کنم و راجع این مباحث یه مرجع کامل تر داشته باشیم. توجه کنید که ترتیب مباحث از ابتدای صفحه تا انتهای صفحه ست. اول 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 – همه شنوندهها را از یک رویداد حذف میکند.
منبع: وبلاگ فرادرس
پروفایل
نکات، تجربیات، محتوای آموزشی و مطالب گردآوری شده در حوزه برنامه نویسی وب