DanLevy.net

חידון: שליטה ב-IO של NodeJS

בחן את הידע שלך בקבצים, זרמים ומאגרים


מוכן לצלול לעולם ה‑IO של NodeJS? 🌊

החידון הזה יבחן את ההבנה שלך בפעולות ה‑IO של Node, החל מפעולות מערכת קבצים בסיסיות ועד למושגי סטרימינג מתקדמים. נסקור buffers, encoding ושיטות עבודה מומלצות לטיפול יעיל בנתונים.

בוא נראה עד כמה אתה מכיר את ה‑streams שלך לעומת ה‑buffers שלך! 🚀

מה הקוד הזה עושה?

const buf = Buffer.alloc(5);
console.log(buf);

Buffer.alloc(size) יוצר Buffer חדש בגודל שצוין וממולא באפסים. הפלט יהיה: <Buffer 00 00 00 00 00>

אם ברצונך ליצור Buffer עם נתונים אקראיים, השתמש ב-Buffer.allocUnsafe(5).

למידע נוסף על הקצאת Buffer

מה יודפס?

const buf = Buffer.from([65]);
console.log(buf.toString());

המספרים במערך מייצגים קודי ASCII:

  • 65: ‘A’

toString() ממיר את הבתים האלה לייצוג מחרוזת באמצעות קידוד UTF-8 כברירת מחדל.

למידע נוסף על קידוד Buffer

מה סדר הפלט?

import fs from 'fs';
fs.readFile('test.txt', 'utf8', (err, data) => {
console.log(data);
});
console.log('Done');

מכיוון ש-readFile הוא אסינכרוני, הקוד ממשיך להתבצע בזמן שהקובץ נקרא. לכן, “Done” יודפס לפני תוכן הקובץ.

כדי לחכות שהקובץ ייקרא קודם, תוכל להשתמש בגרסה מבוססת Promise:

import { promises as fs } from 'fs';
async function read() {
const data = await fs.readFile('test.txt', 'utf8');
console.log(data);
console.log('Done');
}

מה fs.readFileSync() מחזיר כברירת מחדל?

import fs from 'fs';
const content = fs.readFileSync('test.txt');

fs.readFileSync() מחזיר Buffer כברירת מחדל כאשר לא צוין קידוד. אם אתה רוצה מחרוזת, עליך לבצע אחת מהאפשרויות הבאות:

  1. ציין קידוד: fs.readFileSync('test.txt', 'utf8')
  2. המר את ה-Buffer: content.toString()

למידע נוסף על fs.readFileSync במסמכי Node.js

איזו קבוצת אירועים משמשים בדרך כלל עם זרמי Readable?

זרמי Readable פולטים מספר אירועים חשובים:

  • ‘data’: כאשר נתונים זמינים לקריאה
  • ‘end’: כאשר אין עוד נתונים לקריאה
  • ‘error’: כאשר מתרחשת שגיאה
  • ‘close’: כאשר הזרם והמשאב הבסיסי נסגרו
const readable = fs.createReadStream('file.txt');
readable.on('data', chunk => console.log(chunk));
readable.on('end', () => console.log('Done!'));

למד עוד על אירועי זרם

מה הקוד הזה עושה?

import fs from 'fs';
const readable = fs.createReadStream('source.txt');
const writable = fs.createWriteStream('dest.txt');
readable.pipe(writable);

pipe() מחבר זרם קריא לזרם בר-כתיבה, תוך ניהול אוטומטי של לחץ אחורי (backpressure) והעתקת נתונים בנתחים מבלי לטעון את כל הקובץ לזיכרון.

זה חסכוני בזיכרון עבור קבצים גדולים בהשוואה ל-fs.readFile() ואחריו fs.writeFile().

למידע נוסף על pipe()

מה עושה האפשרות recursive?

import fs from 'fs';
fs.mkdirSync('./a/b/c', { recursive: true });

האפשרות recursive: true יוצרת תיקיות אב אם הן לא קיימות. ללא אפשרות זו, ניסיון ליצור ’./a/b/c’ יזרוק שגיאה אם ’./a’ או ’./a/b’ לא קיימים.

זה דומה לפקודת shell mkdir -p.

למידע נוסף על mkdir

מה ייצא?

import { Transform } from 'stream';
const upperCase = new Transform({
transform(chunk, encoding, callback) {
callback(null, chunk.toString().toUpperCase());
}
});
process.stdin
.pipe(upperCase)
.pipe(process.stdout);
// Input: "hello world"

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

  1. מומר למחרוזת
  2. מומר לאותיות גדולות
  3. מועבר ל-stdout

זה יוצר צינור שממיר את כל הקלט לאותיות גדולות.

למד עוד על זרמי טרנספורמציה

כמה פעמים מובטח ש-fs.watch() יופעל כאשר קובץ משתנה?

import fs from 'fs';
fs.watch('test.txt', (eventType, filename) => {
console.log(`${filename} was changed`);
});
// Then modify test.txt once

fs.watch() אינו מובטח שיופעל בדיוק פעם אחת לכל שינוי לוגי בקובץ. לעתים קרובות הוא מופעל מספר פעמים מכיוון שעורכי טקסט רבים:

  1. שומרים לקובץ זמני
  2. משנים את שמו לקובץ היעד

לצפייה אמינה יותר, שקול להשתמש ב:

  • החבילה chokidar
  • דיבוב (debouncing) של הקולבק
  • שימוש ב-fs.watchFile() (אם כי זה פחות יעיל)

למד עוד על fs.watch()

מה הפלט?

const buf1 = Buffer.from('Hello');
const buf2 = Buffer.from('Hello');
console.log(buf1 === buf2);

Buffers מושווים לפי הפניה, לא לפי ערך. למרות שהם מכילים את אותם נתונים, הם אובייקטים שונים.

כדי להשוות תוכן של Buffer, השתמש ב:

buf1.equals(buf2) // true
// or
Buffer.compare(buf1, buf2) === 0 // true

למד עוד על השוואת Buffer

מה המטרה העיקרית של Backpressure בזרמים?

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

דוגמה ל-Backpressure ידני:

readable.on('data', (chunk) => {
const canContinue = writable.write(chunk);
if (!canContinue) {
readable.pause();
writable.once('drain', () => readable.resume());
}
});

pipe() מטפל בזה באופן אוטומטי!

למידע נוסף על Backpressure

מה הקוד הזה עושה?

import fs from 'fs';
fs.symlinkSync('target.txt', 'link.txt');

symlinkSync יוצר קישור סימבולי (כמו קיצור דרך) לקובץ היעד.

הבדלים עיקריים מקישורים קשיחים:

  • יכול לקשר לתיקיות
  • יכול לחצות מערכות קבצים
  • נשבר אם היעד נמחק

כדי ליצור קישור קשיח במקום:

fs.linkSync('target.txt', 'hardlink.txt');

למד עוד על קישורים סימבוליים

באילו מצבים יכולים לפעול סטרימים ב-Node.js?

סטרימים יכולים לפעול ב:

  1. מצב בינארי (ברירת מחדל): עבור buffers ומחרוזות
  2. מצב אובייקט: עבור כל ערך JavaScript

דוגמה למצב אובייקט:

import { Transform } from 'stream';
const objectStream = new Transform({
objectMode: true,
transform(chunk, encoding, callback) {
callback(null, { value: chunk });
}
});

למד עוד על מצבי סטרים

איזה טיפוס הוא הפרמטר fd בקולבק הזה?

import fs from 'fs';
fs.open('test.txt', 'r', (err, fd) => {
console.log(typeof fd);
});

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

שלושת מתארי הקבצים הראשונים שמורים:

  • 0: stdin
  • 1: stdout
  • 2: stderr

תמיד זכור לסגור מתארי קבצים:

fs.close(fd, (err) => {
if (err) throw err;
});

למד עוד על מתארי קבצים

כמה בתים יתפוס המחרוזת הזו ב-UTF-8?

const str = "Hello 🌍";
const buf = Buffer.from(str);
console.log(buf.length);

ב-UTF-8:

  • תווים ASCII (כמו ‘Hello ’) תופסים בית אחד כל אחד
  • האימוג’י של כדור הארץ 🌍 תופס 4 בתים

אז: 5 (Hello) + 1 (רווח) + 4 (🌍) = 10 בתים

כדי לראות את הבתים:

console.log(buf); // <Buffer 48 65 6c 6c 6f 20 f0 9f 8c 8d>

למידע נוסף על קידוד UTF-8

אני מקווה שנהניתם לבחון את הידע שלכם ב-NodeJS IO! רוצים עוד? עיינו באוסף החידונים שלי לעוד אתגרים!