Node.js یک محیط اجرای قدرتمند جاوااسکریپت است که به ما این امکان را میدهد تا کدهای جاوااسکریپتی خود را خارج از مرورگر اجرا کنیم. توجه به این نکته لازم است که بخش اساسی بسیاری از برنامههای Node.js مدیریت انواع فایل است. این فایلها میتوانند Text، JSON، HTML یا سایر فرمتهای فایل را شامل شوند. بنابراین لازم است که با نحوه خواندن و نوشتن فایلها آشنا شویم و آن را یاد بگیریم.
فایلها نقش بسیار مهمی در ذخیرهسازی دادهها دارند. Node.js یک ماژول قدرتمند fs (file system) برای تعامل یکپارچه با این فایلها ارائه میدهد. فرض کنید میخواهیم یک فایل JSON را در Node.js بخوانیم. ماژول fs
میتواند در این مورد به ما کمک کند.
در این مقاله قصد داریم تا عملکردهای اصلی این ماژول را باهم بررسی کنیم، تکنیکهای مختلف برای خواندن انواع فایلهای مختلف را یاد بگیریم.
ماژول File System (fs) یک کامپوننت ضروری از محیط اجرا Node.js است. این ماژول ویژگیهای مختلفی را برای تعامل با فایل سیستمهای کامپیوتر برای ما فراهم میکند.
fs
به ما امکان خواندن، نوشتن، به روز رسانی، حذف و مدیریت فایلها و دایرکتوریها را میدهد. این ماژول به ویژه برای مدیریت عملیات مربوط به فایل در هر دو حالت synchronous و asynchronous مفید است.
در ادامه جنبههای کلیدی ماژول fs
را داریم که عبارتند از:
fs
در هسته خود مجموعهای از APIها را برای تعامل با فایل سیستم ارائه میکند. به عنوان مثال، راههایی برای انجام فعالیتهای اساسی مانند خواندن محتویات فایل، نوشتن دادهها در فایلها، ایجاد دایرکتوریها، حذف فایلها و غیره از جمله این موارد هستند.برای درک بهتر مدیریت فایل و فرآیند خواندن و نوشتن آنها در Node.js مثال زیر را بررسی میکنیم. ما سناریویی را فرض میکنیم که در آن دو فایل داریم: name.json
و address.json
.
محتوای داخل name.json
به شکل زیر است:
[ { "id": 1, "name": "Alice" }, { "id": 2, "name": "Bob" }, { "id": 3, "name": "Charlie" } ]
محتوای داخل address.json
نیز به شکل زیر میباشد:
[ { "id": 1, "address": "123 Main St" }, { "id": 2, "address": "456 Elm St" }, { "id": 3, "address": "789 Oak St" } ]
هدف ما ایجاد یک فایل bio.json
است که اطلاعات id
، name
و address
را ادغام کرده و ساختاری به شرح زیر ایجاد کند:
[ { "id": 1, "name": "Alice", "address": "123 Main St" }, { "id": 2, "name": "Bob", "address": "456 Elm St" }, { "id": 3, "name": "Charlie", "address": "789 Oak St" } ]
کار خود را با ایجاد یک فایل app.js
شروع میکنیم. اولین کاری که انجام میدهیم این است که کتابخانه fs
را import میکنیم:
const fs = require("fs");
در مرحله بعد، دادههای دو فایل را با استفاده از Node.js میخوانیم. ما یک تابع utility میسازیم تا به کمک آن بتوانیم فایلها را به راحتی در محیط Node.js بخوانیم.
async function readJSONFile(filename) { try { const data = await fs.readFile(filename, "utf8"); return JSON.parse(data); } catch (error) { console.error(`Error reading ${filename}: ${error}`); return []; } }
از آنجایی که در مثال خود از فایلهای JSON استفاده میکنیم، یک متد readJSONFile
را در کد خود تعریف کردهایم. این یک تابع جاوااسکریپت asynchronous است که یک نام فایل را به عنوان ورودی میگیرد و هدف آن return کردن محتویات parse JSON شده آن فایل است.
در داخل بلاک try
، ما سعی میکنیم فایل را با استفاده از fs.readFile
در Node با نام فایل مشخص شده و encoding "utf8"
بخوانیم. در صورت موفقیتآمیز بودن، تابع محتوای فایل را به صورت JSON با استفاده از JSON.parse
parse کرده و آن را return میکند.
اگر در حین خواندن یا تجزیه خطایی رخ دهد، اجرای کد در بلاک catch
ادامه پیدا میکند. خطا را با نام فایل و جزئیات ثبت میکند و سپس یک آرایه خالی را به جای آبجکت JSON مورد انتظار return میکند.
مرحله بعدی ایجاد یک تابع main
است که در آن از متد تعریف شده در بالا استفاده میکنیم و دادههای دو فایل را برای ایجاد یک فایل bio.json
ترکیب مینماییم.
async function main() { try { const names = await readJSONFile("names.json"); const addresses = await readJSONFile("address.json"); const bioData = names.map((name) => { const matchingAddress = addresses.find( (address) => address.id === name.id ); return { ...name, ...matchingAddress }; }); await fs.writeFile("bio.json", JSON.stringify(bioData, null, 2)); console.log("bio.json created successfully!"); } catch (error) { console.error("Error combining data:", error); } }
در تابع main
ابتدا دو فایل JSON به نام names.json
و address.json
را با استفاده از تابع readJSONFile
میخوانیم. هر دو فراخوانی readJSONFile
از await
استفاده میکنند، بنابراین این تابع قبل از ادامه کار منتظر میماند تا هر دو فایل خوانده شوند.
سپس، هر name
را با استفاده از یک متد map
تکرار کرده و برای هر کدام یک BioData
جدید میسازیم. در داخل loop، یک addresses
منطبق را از مجموعه آدرسها بر اساس ایندکس با استفاده از متد find
جستجو میکنیم.
این جستجو name.id
را با هر address.id
مقایسه میکند تا زمانی که مطابقت پیدا کند. اگر مطابقت پیدا شود، تابع اطلاعات هر دو فایل را ترکیب میکند. از عملگر spread (...
) برای ادغام تمام ویژگی از هر دو آبجکت در یک آبجکت جدید bioData
استفاده میکند. اگر آدرس منطبقی پیدا نشد، آبجکت bioData
فقط اطلاعات نام را خواهد داشت.
هنگامی که تمام آبجکتهای bioData
آماده شدند، تابع آنها را به عنوان یک فایل JSON جدید با نام bio.json
با استفاده از fs.writeFile
مینویسد. این فرآیند نوشتن نیز از await
استفاده میکند تا اطمینان حاصل شود که فایل مورد نظر قبل از ادامه کار ایجاد شده است.
بلاک try
اجرای روان را تضمین میکند، در حالی که بلاک catch
از هرگونه خطا مانند فایلهای miss شده یا دادههای نادرست مراقبت میکند. اگر خطایی رخ دهد، بلاک catch
یک پیام خطای عمومی و یک خطای خاص همراه با جزئیات برای دیباگ کردن ثبت میکند.
کد تکمیل شده ما به شکل زیر میباشد:
const fs = require("fs").promises; async function readJSONFile(filename) { try { const data = await fs.readFile(filename, "utf8"); return JSON.parse(data); } catch (error) { console.error(`Error reading ${filename}: ${error}`); return []; } } async function main() { try { const names = await readJSONFile("names.json"); const addresses = await readJSONFile("address.json"); const bioData = names.map((name) => { const matchingAddress = addresses.find( (address) => address.id === name.id ); return { ...name, ...matchingAddress }; }); await fs.writeFile("bio.json", JSON.stringify(bioData, null, 2)); console.log("bio.json created successfully!"); } catch (error) { console.error("Error combining data:", error); } } // Execute the main method main();
میتوانیم با استفاده از دستور زیر برنامه را اجرا کنیم:
node app.js
هنگامی که برنامه اجرا میشود، اگر همه چیز به خوبی پیش برود، گزارشهای زیر را در ترمینال مشاهده میکنیم:
bio.json created successfully!
این نشان میدهد که فایل bio.json
با موفقیت ایجاد شده است. محتوای داخل فایل باید به شکل زیر باشد:
[ { "id": 1, "name": "Alice", "address": "123 Main St" }, { "id": 2, "name": "Bob", "address": "456 Elm St" }, { "id": 3, "name": "Charlie", "address": "789 Oak St" } ]
یکی از کارهایی که برای مدیریت فایل در Node.js باید انجام دهیم خواندن آن است. برای خواندن فایلها در Node.js، بیشتر از این دو متد اصلی استفاده میکنیم: fs.readFile()
و fs.readFileSync()
.
متد fs.readFile()
در Node.js یک متد asynchronous است. این متد محتوای کل فایل را بدون مسدود کردن سایر عملیاتها میخواند. همین موضوع آن را برای سناریوهایی که عملیات non-blocking I/O ضروری هستند، مناسب میکند.
به زبان ساده، این تابع به سایر عملیاتها اجازه میدهد تا زمانی که خواندن انجام میشود ادامه پیدا کنند. متد fs.readFile()
سه پارامتر زیر را میگیرد:
path
: مسیر فایلی که باید خوانده شود.encoding
: یک گزینه اختیاری است و encoding فایل را مشخص میکند (به عنوان مثال، "utf8"
). اگر ارائه نشده باشد، به طور پیشفرض مقدار "utf8"
را میگیرد.error
، data
و buffer
.به عنوان مثال:
const fs = require("fs"); fs.readFile("data.txt", "utf8", (err, data) => { if (err) { console.error(err); } else { console.log(data); // data will be a string containing the content of the file } });
متد fs.readFileSync()
یک متد synchronous است. محتوای فایل را به صورت synchronous میخواند و اجرای سایر کدها را تا زمانی که فایل به طور کامل خوانده شود، متوقف میکند. این روش در سناریوهایی که نیاز به پردازشهای همزمان داریم مفید میباشد.
این متد فقط دو پارامتر میگیرد:
path
: مسیر فایلی که باید خوانده شود.encoding
: یک گزینه اختیاری است و encoding فایل را مشخص میکند (به عنوان مثال، "utf8"
). اگر ارائه نشده باشد، به طور پیشفرض مقدار "utf8"
را میگیرد.به عنوان مثال:
const fs = require("fs"); try { const data = fs.readFileSync("data.txt", "utf8"); console.log(data); // data will be a string containing the content of the file } catch (err) { console.error(err); }
برای درک تفاوت بین متدهای readFile
و readFileSync
Node.js، دو برنامه مینویسیم و جریان اجرای آنها را تحت نظر میگیریم.
ابتد با متد fs.readFile()
شروع میکنیم.
const fs = require("fs"); fs.readFile("example.txt", "utf8", (err, data) => { console.log("Content from readFile:", data); }); console.log("Completed reading file content asynchronously");
خروجی به شکل زیر میباشد:
Completed reading file content asynchronously Content from readFile: freeCodeCamp is awesome!
به دلیل ماهیت asynchronous متد fs.readFile()
، کد بعد از fs.readFile()
منتظر پایان عملیات خواندن فایل نمیماند. بنابراین پیام "Completed reading file content synchronously"
بلافاصله در کنسول ثبت میشود و نشان میدهد که کد بعدی بدون منتظر ماندن برای تکمیل فایل خوانده شده، به اجرا ادامه میدهد.
در نهایت، زمانی که عملیات خواندن فایل به پایان رسید، تابع callback محتوای فایل را اجرا و ثبت میکند.
در ادامه، اجرای متد fs.readFileSync()
را مشاهده میکنیم.
const fs = require("fs"); const data = fs.readFileSync("data.txt", "utf8"); console.log("Content from readFileSync:", data); console.log("Completed reading file content synchronously");
خروجی به شکل زیر میباشد:
Content from readFileSync: freeCodeCamp is awesome! Completed reading file content synchronously
در مقابل، متد fs.readFileSync()
فایل data.txt
را به صورت synchronous میخواند و اجرای کد را تا زمانی که فایل به طور کامل خوانده شود، مسدود میکند. در نتیجه، اجرای کد تنها پس از پایان عملیات خواندن فایل ادامه پیدا میکند.
به همین دلیل، پس از خواندن موفقیتآمیز محتوای فایل، پیام "Completed reading file content synchronously"
در کنسول ثبت میشود.
اکنون تفاوت بین این دو متد را میدانیم. درک این تفاوت مهم است زیرا بر روی جریان برنامه تأثیر میگذارد.
خواندن یک فایل متنی در Node.js بسیار ساده است. مثالی را در نظر میگیریم که فایلی به نام message.txt
با محتوای زیر داریم:
Learn Node.js with freeCodeCamp
اکنون میخواهیم محتویات این فایل را بخوانیم. میتوانیم این کار را به این صورت انجام دهیم:
const fs = require("fs"); fs.readFile("message.txt", "utf8", (err, data) => { if (err) { console.log(err); } else { console.log(data); } });
تابع callback محتوای فایل را در یک متغیر data
return میکند. از آنجایی که encoding را روی "utf8"
تنظیم کردهایم، مقدار دادهها یک رشته است. بنابراین میتوانیم عملیات رشتهای را روی متغیر data
انجام دهیم.
const fs = require("fs"); fs.readFile("message.txt", "utf8", (err, data) => { if (err) { console.log(err); } else { let splittedWords = data.split(" "); console.log(splittedWords); } });
در کد بالا، متغیر data
را با استفاده از یک split space میکنیم. بنابراین splittedWords
یک آرایه رشتهای خواهد بود که حاوی مقدار زیر میباشد:
[ 'Learn', 'Node.js', 'with', 'freeCodeCamp' ]
خواندن فایلهای HTML از روشی مشابه برای خواندن فایلهای Text در Node.js پیروی میکند. ما میتوانیم از ماژول fs
برای خواندن فایلهای HTML استفاده کنیم:
const fs = require("fs"); fs.readFile("index.html", "utf8", (err, data) => { if (err) { console.log(err); } else { console.log(data); } });
سپس میتوانیم از محتوای HTML برای پردازش بیشتر، مانند رندر کردن آن با پکیج http
در Node.js استفاده کنیم.
خواندن فایلها با URL در Node.js شامل مراحل اضافی فراتر از ماژول native fs است. به طور معمول، ما باید از ماژولهای اضافی مانند http
یا axios
برای دریافت محتوای فایل از یک URL استفاده کنیم.
const fs = require("fs"); const https = require("https"); const file = fs.createWriteStream("data.txt"); https.get( "https://example-files.online-convert.com/document/txt/example.txt", (response) => { var stream = response.pipe(file); stream.on("finish", function () { console.log("done"); }); } );
ابتدا، یک جریان قابل نوشتن با نام فایل، مرتبط با فایل لوکال data.txt
تنظیم مینماییم. سپس از ماژول https
در Node.js برای انجام یک درخواست HTTP GET
به URL مشخص شده استفاده میکنیم. هنگامی که ما پاسخی از سرور دریافت میکنیم، متد GET
یک تابع callback را فعال میکند.
در داخل تابع callback، اسکریپت پاسخ را مستقیماً به جریان قابل نوشتن ارسال میکند. این عملیات به طور موثر دادههای دریافت شده از سرور remote را به فایل لوکال data.txt
هدایت میکند و اساساً محتوا را به صورت همزمان دانلود کرده و مینویسد.
در نهایت، یک event listener برای event "finish"
در جریان تنظیم میکنیم. این event زمانی فعال میشود که تمام دادهها با موفقیت در فایل نوشته شوند. پس از تکمیل، اسکریپت "done"
را در کنسول ثبت میکند که نشاندهنده موفقیتآمیز بودن دانلود و نوشتن فایل میباشد.
دیدیم که چگونه میتوانیم یک فایل JSON را با استفاده از ماژول fs
بخوانیم. فرض کنید میخواهیم فایل bio.json
را که قبلاً ایجاد کردهایم، بخوانیم. دادههای آن مانند زیر میباشد:
[ { "id": 1, "name": "Alice", "address": "123 Main St" }, { "id": 2, "name": "Bob", "address": "456 Elm St" }, { "id": 3, "name": "Charlie", "address": "789 Oak St" } ]
در Node.js، ما JSON را به صورت زیر میخوانیم:
const fs = require("fs"); fs.readFile("bio.json", "utf8", (err, data) => { if (err) { console.log(err); } else { console.log(data); } });
با این کار، دادههای JSON در متغیر data
به عنوان یک رشته ذخیره میشوند. در صورت تمایل میتوانیم از آن برای پردازش بیشتر استفاده کنیم. فرض کنید، میخواهیم اطلاعات کاربر را چاپ کنیم:
const fs = require("fs"); fs.readFile("bio.json", "utf8", (err, data) => { if (err) { console.log(err); } else { const users = JSON.parse(data); users.forEach((user) => { console.log(`${user.name} with ID ${user.id} lives at ${user.address}`); }); } });
خروجی به شکل زیر میباشد:
Alice with ID 1 lives at 123 Main St Bob with ID 2 lives at 456 Elm St Charlie with ID 3 lives at 789 Oak St
در کد بالا، ابتدا متغیر data
که رشته است را به parse JSON کرده و آن را در متغیر users
ذخیره میکنیم. سپس روی متغیر users
یک loop ایجاد میکنیم تا پیام مورد نیاز را ثبت کنیم.
fs.promises
مجموعه ای از توابع asynchronous را برای مدیریت و تعامل با فایل سیستم در Node.js فراهم میکند. این توابع بر اساس promiseها هستند و روشی خوانا و کارآمدتر برای مدیریت عملیات asynchronous در مقایسه با توابع callback ارائه میدهند.
با استفاده از fs.promises
، ما نیازی به اضافه کردن callbackهای تودرتو نداریم، به این معنی که میتوانیم از ایجاد callback hell جلوگیری کنیم.
یک عملیات readFile بیسیک با fs.promises
به شکل زیر میباشد:
const fs = require("fs").promises; async function readTextFile() { try { const data = await fs.readFile("data.txt", "utf8"); console.log(data); } catch (err) { console.error(err); } } readTextFile();
ما در این مقاله سعی کردیم تا تکنیکهای ضروری برای مدیریت فایل در Node.js با استفاده از ماژول fs
را بررسی کنیم. خواندن synchronous و asynchronous فایلها با متدهایی مانند fs.readFile()
و fs.readFileSync()
را یاد گرفتیم، همینطور با پردازش فرمتهای فایل مختلف مانند Text، HTML، JSON و حتی خواندن فایلها از URL آشنا شدیم. علاوه بر این، fs.promises
که روشی سادهتر برای مدیریت عملیات فایل با استفاده از توابع asynchronous است را باهم بررسی کردیم.