ארבע דרכים לקרוא קובץ טקסט ב Node.JS

23/10/2018

פוסט זה מכיל טיפ קצר על עבודה עם Node.JS. כדי ללמוד Node.JS הרבה יותר לעומק אני ממליץ לכם לבדוק את קורס Node.JS כאן באתר. הקורס כולל עשרות שיעורי וידאו מוקלטים והמון תרגול שילוו אתכם מהצעד הראשון בעבודה עם Node.JS ועד הנושאים המתקדמים.

נוד נועד לרוץ על השרת ולכן יש לו גישה למחשב המקומי, מה שאנחנו לא מוצאים ב JavaScript שרץ על הדפדפן. אחד הדברים הראשונים שאנשים רוצים לעשות עם גישה זו הוא לכתוב ולקרוא מקבצים כפי שנראה בשיעור זה.

1. ממשק סינכרוני לעומת ממשק אסינכרוני

מודול הכתיבה לקבצים נקרא fs והוא מודול מערכת בנוד. בגלל שיקולים היסטוריים יש 3 דרכים מרכזיות לעבוד עם מודול זה:

  1. האפשרות הראשונה היתה לעבוד עם מערכת הקבצים באמצעות מנגנון Callbacks. אנחנו מבקשים לבצע פעולה כמו לכתוב שורה לקובץ, נוד יבצע את הפעולה ויפעיל פונקציית Callbacks שלנו כשהפעולה תסתיים. הדבר מאפשר לכתוב שרתים יעילים יותר שכן בזמן שנוד כותב לקובץ הוא חופשי לטפל בלקוחות נוספים.

  2. הרבה פעמים פעולות על מערכת הקבצים הן די מהירות כי מערכת הקבצים מחוברת מקומית לשרת, או שאנחנו בכלל כותבים קוד לסקריפט שלא ירוץ בתוך שרת עמוס אלא על מכונת פיתוח. בשביל זה פיתחו בנוד ממשק נוסף של פעולות סינכרוניות על קבצים. אלא אותן הפעולות שנראה כך במקום לקבל Callback ולהפעיל אותו בסיום הפעולות חוזרות רק אחרי שהפקודה באמת הסתיימה.

  3. גירסא 10 של נוד הוסיפה תמיכה במנגנון Promises. אני ממליץ לכם לוודא שאתם מכירים את המנגנון (או ללכת ללמוד עליו מקורס ES6 שלנו). בזכות מנגנון Promise ו async/await אנחנו יכולים לכתוב קוד אסינכרוני שיראה כמו קוד סינכרוני ולהינות משני העולמות.

  4. בנוסף נוד כולל מספר Utility Classes שאמורים לעזור לנו לקרוא ולכתוב טקסט לקבצים בצורה יותר ידידותית.

התוצאה היא אוקיינוס של אפשרויות שהרבה מהן יתנו תוצאות דומות. בשיעור זה אני מעדיף להתמקד ולכן אראה רק את הדרכים המרכזיות לביצוע ארבעת הפעולות ומספר שיקולים מתי לבחור בכל דרך.

2. קריאת קבצי טקסט לזיכרון

שתי האפשרויות המרכזיות לקריאת קבצי טקסט הן לקרוא את הקובץ כולו לזיכרון במכה אחת או לקרוא את הקובץ שורה אחר שורה. נשתמש באפשרות הראשונה לקבצים קטנים יותר וככל שגודל הקובץ גדול או שאנחנו לא יודעים אם יהיה גדול נעדיף לקרוא אותו לאט שורה אחר שורה.

כדי לקרוא קובץ טקסט לזיכרון נשתמש בפקודה fs.readFile או fs.readFileSync. הגירסא האסינכרונית נראית כך והיא מדפיסה את כל תוכן הקובץ /etc/shells:

const fs = require('fs');
fs.readFile('/etc/shells', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

את אותו קוד אפשר לרשום גם בצורה סינכרונית עם:

const fs = require('fs');
const data = fs.readFileSync('/etc/shells', 'utf8');
console.log(data);

מבחינת טיפול בשגיאות - את הקוד הסינכרוני אפשר לעטוף ב try/catch כדי לתפוס את השגיאה. בגירסא האסינכרונית זה לא אפשרי. בשיעור הבא נרחיב על טיפול בשגיאות ונראה למה ומה כן אפשר לעשות.

אפשרות שלישית לקריאת קובץ במלואו היא באמצעות Promises. הפעם עלינו לטעון מודול אחר של נוד שנקרא fs/promises ואז נוכל לכתוב:

const fsp = fs.promises;
async function demo3() {
  const data = await fsp.readFile('/etc/shells', 'utf8');
  console.log(data);
}

demo3();

ממשק זה עדיין נמצא במצב Experimental ולכן אתם עשויים לקבל הערת אזהרה כשמריצים את הקוד.

3. קריאת קבצי טקסט שורה-שורה

כדי לקרוא קובץ טקסט שורה-שורה נרצה להשתמש במודול מערכת נוסף שנקרא readline. מודול זה מטפל בשבילנו בכל הנושא של קריאת מידע עד תו ירידת השורה כולל שימוש נכון בחוצצים. התוכנית הבאה קוראת את הקובץ /etc/shells שלנו הפעם שורה-שורה באמצעות readline:

const readline = require('readline');
const fs = require('fs');

const rl = readline.createInterface({
  input: fs.createReadStream('/etc/shells'),
  crlfDelay: Infinity
});

rl.on('line', (line) => {
  console.log(line);
});

שימו לב שבעבודה עם Streams וקריאת הקובץ שורה-שורה אין צורך לציין Encoding. המודול readline באופן אוטומטי מגדיר קידוד utf8 מאחר וזה מודול ייעודי לעבודה עם טקסט. הגדרת crlfDelay אומרת שבכל מקרה שנקבל את תווי ירידת השורה \r ואחריו \n נתיחס אליהם יחד כתו אחד לירידת שורה. אפשר להגדיר כאן גם ערכים מספריים (ברירת המחדל למשל היא 100) כדי לאפשר לנוד להתיחס לתווים אלה כשני תווים נפרדים. הגדרה כזו רלוונטית בקריאה מתוך סביבה אינטרקטיבית אבל לא בעבודה עם קבצים.

האירוע line שנשלח מ readline מציין ששורה חדשה זמינה לטיפול. חוץ מ line המודול מספק אירוע בשם close שנקרא כשהקלט מסתיים והקובץ נסגר.