תכנות אסינכרוני
תכנות אסינכרוני הוא הלב של Node.js. זה מה שמאפשר לו להיות מהיר ויעיל.
למה צריך אסינכרוניות?
הבעיה עם קוד סינכרוני
// Code סינכרוני - חוסם!
const data = readFileSync('big-file.txt'); // ממתין 2 שניות
console.log('קובץ נקרא');
processOtherRequest(); // מתחיל רק אחרי שהקובץ נקרא
// בזמן ש-readFileSync עובד, השרת לא יכול לעשות כלום אחר!
הפתרון האסינכרוני
// Code אסינכרוני - לא חוסם!
readFile('big-file.txt', (data) => {
console.log('קובץ נקרא');
});
processOtherRequest(); // מתחיל מיד!
// בזמן שהקובץ נקרא, השרת ממשיך לעבוד על דברים אחרים
סינכרוני: [קריאת קובץ......] → [עיבוד בקשה] → [סיום]
זמן: 3 שניות
אסינכרוני: [התחלת קריאה]
[עיבוד בקשה] ← קורה במקביל!
[קריאה הסתיימה] זמן: 2 שניות
Callbacks
הדרך הראשונה (והישנה ביותר) לטפל באסינכרוניות.
מהו Callback?
פונקציה שנשלחת כפרמטר ומופעלת כשהפעולה מסתיימת.
const fs = require('fs');
// קריאת קובץ עם callback
fs.readFile('file.txt', 'utf8', (error, data) => {
// הפונקציה הזו תרוץ כשהקריאה תסתיים
if (error) {
console.error('שגיאה:', error.message);
return;
}
console.log('תוכן הקובץ:', data);
});
console.log('זה יודפס ראשון!');
מוסכמה: Error-First Callback
ב-Node.js, ה-callback תמיד מקבל את השגיאה כפרמטר הראשון:
function doSomethingAsync(callback) {
// אם הצליח
callback(null, result);
// אם נכשל
callback(new Error('משהו השתבש'));
}
// שימוש
doSomethingAsync((error, result) => {
if (error) {
// טיפול בשגיאה
return;
}
// שימוש בתוצאה
});
Callback Hell 😱
כשיש הרבה פעולות אסינכרוניות בזו אחר זו:
// ❌ Callback Hell - קשה לקריאה ולתחזוקה
fs.readFile('file1.txt', 'utf8', (err1, data1) => {
if (err1) throw err1;
fs.readFile('file2.txt', 'utf8', (err2, data2) => {
if (err2) throw err2;
fs.readFile('file3.txt', 'utf8', (err3, data3) => {
if (err3) throw err3;
fs.writeFile('result.txt', data1 + data2 + data3, (err4) => {
if (err4) throw err4;
console.log('סיימנו!');
});
});
});
});
הפתרון? Promises!
Promises
Promise הוא אובייקט שמייצג פעולה אסינכרונית שעוד לא הסתיימה.
שלושה מצבים של Promise
┌─────────────────────────────────────────────────────┐
│ Promise │
├─────────────────────────────────────────────────────┤
│ Pending (ממתין) → Fulfilled (הצליח) ──→ .then() │
│ → Rejected (נכשל) ────→ .catch() │
└─────────────────────────────────────────────────────┘
יצירת Promise
// Create Promise ידנית
const myPromise = new Promise((resolve, reject) => {
// פעולה אסינכרונית
setTimeout(() => {
const success = true;
if (success) {
resolve('הפעולה הצליחה!'); // מעביר לתוצאה
} else {
reject(new Error('הפעולה נכשלה')); // מעביר לשגיאה
}
}, 1000);
});
שימוש ב-Promise
myPromise
.then(result => {
console.log(result); // 'הפעולה הצליחה!' אחרי שנייה
})
.catch(error => {
console.error(error.message);
})
.finally(() => {
console.log('סיום (תמיד מתבצע)');
});
שרשור Promises
const fs = require('fs').promises; // גרסת Promises של fs
// ✅ שרשור קריא ונקי
fs.readFile('file1.txt', 'utf8')
.then(data1 => {
console.log('קובץ 1:', data1);
return fs.readFile('file2.txt', 'utf8');
})
.then(data2 => {
console.log('קובץ 2:', data2);
return fs.readFile('file3.txt', 'utf8');
})
.then(data3 => {
console.log('קובץ 3:', data3);
})
.catch(error => {
console.error('שגיאה:', error.message);
});
async/await
הדרך הכי מודרנית וקריאה לעבוד עם קוד אסינכרוני.
הרעיון הבסיסי
// .then() lots
fetchData()
.then(data => process(data))
.then(result => save(result));
// ...אפשר לכתוב כמו קוד סינכרוני!
async function main() {
const data = await fetchData();
const result = await process(data);
await save(result);
}
חוקים חשובים
// 1. await יכול להופיע רק בתוך פונקציית async
async function myFunction() {
const result = await somePromise();
}
// 2. פונקציית async תמיד מחזירה Promise
async function greet() {
return 'שלום';
}
greet().then(msg => console.log(msg)); // 'שלום'
// 3. אפשר להשתמש ב-await עם כל Promise
const fs = require('fs').promises;
async function readFiles() {
const data = await fs.readFile('file.txt', 'utf8');
console.log(data);
}
טיפול בשגיאות עם try/catch
async function readFile() {
try {
const data = await fs.readFile('file.txt', 'utf8');
console.log(data);
return data;
} catch (error) {
console.error('שגיאה בקריאת הקובץ:', error.message);
throw error; // העברת השגיאה הלאה (אופציונלי)
}
}
דוגמה מלאה
const fs = require('fs').promises;
async function processFiles() {
try {
// קריאת שלושה קבצים בזה אחר זה
const data1 = await fs.readFile('file1.txt', 'utf8');
const data2 = await fs.readFile('file2.txt', 'utf8');
const data3 = await fs.readFile('file3.txt', 'utf8');
// עיבוד
const result = data1 + data2 + data3;
// כתיבה
await fs.writeFile('result.txt', result);
console.log('הכל הושלם!');
} catch (error) {
console.error('שגיאה:', error.message);
}
}
// הפעלה
processFiles();
Promise.all ו-Promise.race
Promise.all - הרצה במקביל
ממתין שכל ה-Promises יסתיימו.
const fs = require('fs').promises;
async function readAllFiles() {
// ❌ לא יעיל - קובץ אחרי קובץ
const data1 = await fs.readFile('file1.txt', 'utf8'); // 1 שנייה
const data2 = await fs.readFile('file2.txt', 'utf8'); // 1 שנייה
const data3 = await fs.readFile('file3.txt', 'utf8'); // 1 שנייה
// סה"כ: 3 שניות
// ✅ יעיל - במקביל!
const [data1, data2, data3] = await Promise.all([
fs.readFile('file1.txt', 'utf8'),
fs.readFile('file2.txt', 'utf8'),
fs.readFile('file3.txt', 'utf8')
]);
// סה"כ: ~1 שנייה
}
⚠️ שימו לב: אם Promise אחד נכשל, כל ה-Promise.all נכשל!
Promise.allSettled - גם אם יש כשלונות
const results = await Promise.allSettled([
fs.readFile('file1.txt', 'utf8'),
fs.readFile('file2.txt', 'utf8'),
fs.readFile('nonexistent.txt', 'utf8') // יכשל
]);
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('הצליח:', result.value);
} else {
console.log('נכשל:', result.reason.message);
}
});
Promise.race - הראשון שמסיים מנצח
// Usefull ל-timeout
async function fetchWithTimeout(url, timeout) {
return Promise.race([
fetch(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeout)
)
]);
}
try {
const response = await fetchWithTimeout('https://api.example.com', 5000);
} catch (error) {
console.log('הבקשה ארכה יותר מדי זמן');
}
Promise.any - הראשון שמצליח
// Search את השרת הזמין הראשון
const result = await Promise.any([
fetch('https://server1.com'),
fetch('https://server2.com'),
fetch('https://server3.com')
]);
// מחזיר את התגובה מהשרת שענה ראשון
דפוסים נפוצים
IIFE (Immediately Invoked Function Expression)
// async הפעלה מיידית של פונקציית
(async () => {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.error(error);
}
})();
עיבוד מערך אסינכרוני
const urls = ['url1', 'url2', 'url3'];
// ❌ forEach לא מחכה ל-await!
urls.forEach(async (url) => {
const data = await fetch(url); // לא מחכה!
});
// ✅ אופציה 1: for...of (בזה אחר זה)
for (const url of urls) {
const data = await fetch(url);
console.log(data);
}
// ✅ אופציה 2: Promise.all (במקביל)
const results = await Promise.all(
urls.map(url => fetch(url))
);
Sequential vs Parallel
// Sequential (סדרתי) - כל אחד ממתין לקודם
async function sequential() {
const a = await task1(); // 1 שנייה
const b = await task2(); // 1 שנייה
const c = await task3(); // 1 שנייה
// סה"כ: 3 שניות
}
// Parallel (מקבילי) - כולם יחד
async function parallel() {
const [a, b, c] = await Promise.all([
task1(), // 1 שנייה
task2(), // 1 שנייה
task3() // 1 שנייה
]);
// סה"כ: ~1 שנייה
}
המרת Callbacks ל-Promises
שימוש ב-util.promisify
const util = require('util');
const fs = require('fs');
// המרה ידנית
const readFilePromise = util.promisify(fs.readFile);
async function main() {
const data = await readFilePromise('file.txt', 'utf8');
console.log(data);
}
// או פשוט להשתמש ב-fs.promises
const fsPromises = require('fs').promises;
עטיפה ידנית
function readFileAsync(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}
סיכום
| שיטה | יתרונות | חסרונות |
|---|---|---|
| Callbacks | פשוט, עובד בכל מקום | Callback Hell |
| Promises | שרשור נקי | עדיין מעט מסורבל |
| async/await | קריא כמו קוד סינכרוני | דורש try/catch |
כללי אצבע
- השתמשו ב-async/await - הכי קריא ונוח
- תמיד טפלו בשגיאות - try/catch או .catch()
- השתמשו ב-Promise.all - כשצריך לעשות דברים במקביל
- הימנעו מ-forEach עם async - השתמשו ב-for...of או map
← חזרה למודולים | → חזרה לדף הראשי
בדקו את עצמכם
נסו לענות לבד לפני שאתם פותחים את התשובה.
-
מה הבעיה בקריאה סינכרונית (למשל readFileSync) בשרת?
הצגת התשובה
תשובה א. בשרת משתמשים תמיד בגרסאות ה-Promise עם await.
-
איך מריצים כמה פעולות בלתי תלויות במקביל?
הצגת התשובה
תשובה ג. Promise.all מחכה לכולן יחד, במקום אחת אחרי השנייה.