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

فایل‌ها نقش بسیار مهمی در ذخیره‌سازی داده‌ها دارند. Node.js یک ماژول قدرتمند fs (file system) برای تعامل یکپارچه با این فایل‌ها ارائه می‌دهد. فرض کنید می‌خواهیم یک فایل JSON را در Node.js بخوانیم. ماژول fs می‌تواند در این مورد به ما کمک کند.

در این مقاله قصد داریم تا عملکردهای اصلی این ماژول را باهم بررسی کنیم، تکنیک‌های مختلف برای خواندن انواع فایل‌های مختلف را یاد بگیریم.

ماژول fs در Node.js

ماژول File System (fs) یک کامپوننت ضروری از محیط اجرا Node.js است. این ماژول ویژگی‌های مختلفی را برای تعامل با فایل سیستم‌های کامپیوتر برای ما فراهم می‌کند.

fs به ما امکان خواندن، نوشتن، به روز رسانی، حذف و مدیریت فایل‌ها و دایرکتوری‌ها را می‌دهد. این ماژول به ویژه برای مدیریت عملیات مربوط به فایل در هر دو حالت synchronous و asynchronous مفید است.

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

  1. ماژول fs در هسته خود مجموعه‌ای از APIها را برای تعامل با فایل سیستم ارائه می‌کند. به عنوان مثال، راه‌هایی برای انجام فعالیت‌های اساسی مانند خواندن محتویات فایل، نوشتن داده‌ها در فایل‌ها، ایجاد دایرکتوری‌ها، حذف فایل‌ها و غیره از جمله این موارد هستند.
  2. این ماژول شامل روش‌های synchronous و asynchronous برای تعامل با فایل‌ها است. روش‌های synchronous اجرای برنامه را تا زمانی که عملیات کامل شود مسدود می‌کند. اما روش‌های asynchronous برای سناریوهایی ایده‌آل است که در آن نیاز به انجام کارهای همزمان بدون توقف اجرای کل برنامه داریم.
  3. همچنین این ماژول از مدیریت دایرکتوری‌ها مانند ایجاد دایرکتوری‌ها، حذف دایرکتوری‌ها و فهرست کردن محتویات دایرکتوری پشتیبانی می‌کند.
  4. علاوه بر این، این ماژول از کار با file streamها نیز پشتیبانی می‌کند و امکان مدیریت کارآمد فایل‌های بزرگ را با خواندن یا نوشتن داده‌ها در chunkها، بدون لود کردن کل محتوا در حافظه فراهم می‌نماید. همچنین استفاده از بافرها برای مدیریت داده‌های باینری را نیز تسهیل می‌کند.

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

برای درک بهتر مدیریت فایل و فرآیند خواندن و نوشتن آن‌ها در 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"
  }
]

قدم اول import پکیج‌های path و fs از Node.js

کار خود را با ایجاد یک فایل 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

مرحله بعدی ایجاد یک تابع 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 باید انجام دهیم خواندن آن است. برای خواندن فایل‌ها در Node.js، بیشتر از این دو متد اصلی استفاده می‌کنیم: fs.readFile() و fs.readFileSync().

متد ()fs.readFile

متد fs.readFile() در Node.js یک متد asynchronous است. این متد محتوای کل فایل را بدون مسدود کردن سایر عملیات‌ها می‌خواند. همین موضوع آن را برای سناریوهایی که عملیات non-blocking I/O ضروری هستند، مناسب می‌کند.

به زبان ساده، این تابع به سایر عملیات‌ها اجازه می‌دهد تا زمانی که خواندن انجام می‌شود ادامه پیدا کنند. متد fs.readFile() سه پارامتر زیر را می‌گیرد:

به عنوان مثال:

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

متد fs.readFileSync() یک متد synchronous است. محتوای فایل را به صورت synchronous می‌خواند و اجرای سایر کدها را تا زمانی که فایل به طور کامل خوانده شود، متوقف می‌کند. این روش در سناریوهایی که نیاز به پردازش‌های همزمان داریم مفید می‌باشد.

این متد فقط دو پارامتر می‌گیرد:

به عنوان مثال:

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);
}

مقایسه متد ()fs.readFile و ()fs.readFileSync

برای درک تفاوت بین متدهای 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" در کنسول ثبت می‌شود.

اکنون تفاوت بین این دو متد را می‌دانیم. درک این تفاوت مهم است زیرا بر روی جریان برنامه تأثیر می‌گذارد.

نحوه خواندن یک فایل Text

خواندن یک فایل متنی در 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

خواندن فایل‌های 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

خواندن فایل‌ها با 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

دیدیم که چگونه می‌توانیم یک فایل 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

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 است را باهم بررسی کردیم.