افهم الـ Asynchronous JavaScript والـ Callbacks والـ Promises والـ Async Await
تم التحديث: ٢٧ مارس ٢٠٢٦
ملخص
تطور عدم التزامن في JavaScript من الـ callbacks (التي أدت إلى تداخل "هرم الفناء" pyramid-of-doom) إلى الـ Promises (القابلة للتسلسل، مع معالجة أفضل للأخطاء) ثم إلى async/await (كود يبدو متزامناً مع كامل قوة المهام غير المتزامنة) — كل تجريد يبني على ما قبله، ويعالج القيود السابقة.
البرمجة غير المتزامنة أساسية في JavaScript. سواء كنت تجلب بيانات، أو تقرأ ملفات، أو تتعامل مع تفاعلات المستخدم، فإن العمليات غير المتزامنة موجودة في كل مكان. لكن المهام غير المتزامنة لم تكن دائماً أنيقة — فقد عانى المطورون ذات يوم من "جحيم الـ callback"، وهرم الفناء، ومعالجة الأخطاء المتشابكة. يتتبع هذا الدليل التطور من الـ callbacks إلى الـ Promises وصولاً إلى async/await، موضحاً كيف حل كل ابتكار المشكلات السابقة مع تقديم أنماط جديدة. بنهاية الدليل، لن تفهم فقط كيفية استخدام async/await، بل ستعرف لماذا هو الخيار الافتراضي في عام 2026.
Callbacks: النمط الأصلي
الـ Callbacks هي دوال يتم تمريرها كوسيطات (arguments) ليتم تنفيذها بعد اكتمال العملية.
نمط الـ Callback الأساسي
// Callback function (synchronous use)
const processData = (data, callback) => {
callback(data.toUpperCase());
};
processData('hello', result => {
console.log(result); // 'HELLO'
});
// Asynchronous callback
const fetchData = (url, callback) => {
setTimeout(() => {
callback({ id: 1, name: 'Alice' });
}, 100);
};
fetchData('/API/user', user => {
console.log('User:', user.name);
});
جحيم الـ Callback (هرم الفناء)
تصبح الـ Callbacks إشكالية عندما تتداخل العمليات — كل مستوى يزيد من الإزاحة للداخل، مما يخلق كوداً يصعب قراءته.
// Callback hell: deeply nested callbacks
getUser(userId, user => {
getPosts(user.id, posts => {
getComments(posts[0].id, comments => {
getAuthor(comments[0].authorId, author => {
console.log('Final author:', author.name);
// We're 4 levels deep!
});
});
});
});
// Problems:
// 1. Hard to read and maintain
// 2. Error handling is awkward (need try-catch at each level)
// 3. Code flows right, not down
// 4. Difficult to parallelize operations
معالجة الأخطاء مع الـ Callbacks
تفتقر الـ Callbacks إلى معالجة مدمجة للأخطاء — يجب عليك تمرير كائنات الخطأ يدوياً أو استخدام callbacks منفصلة للأخطاء.
// Convention: error-first callbacks
const readFile = (filename, callback) => {
setTimeout(() => {
if (filename === 'missing.txt') {
callback(new Error('File not found'), null);
} else {
callback(null, 'File contents');
}
}, 100);
};
readFile('data.txt', (error, data) => {
if (error) {
console.error('Error:', error.message);
} else {
console.log('Data:', data);
}
});
// Nested error handling (tedious)
readFile('file1.txt', (err1, data1) => {
if (err1) {
console.error(err1);
return;
}
readFile('file2.txt', (err2, data2) => {
if (err2) {
console.error(err2);
return;
}
console.log('Both files:', data1, data2);
});
});
Promises: مهام غير متزامنة قابلة للتسلسل
تمثل الـ Promises قيمة مستقبلية وتوفر .then()، و .catch()، و .finally() لتركيب كود أكثر نظافة.
إنشاء واستخدام الـ Promises
// Creating a Promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Success!');
// or: reject(new Error('Failed!'));
}, 100);
});
// Consuming a Promise
promise
.then(result => {
console.log(result); // 'Success!'
return result + ' More work';
})
.then(result => {
console.log(result); // 'Success! More work'
})
.catch(error => {
console.error('Error:', error.message);
})
.finally(() => {
console.log('Operation complete');
});
التسلسل مقابل التداخل
تقضي الـ Promises على التداخل من خلال السماح بالتسلسل (chaining).
// Without Promises (callback hell)
getUser(userId, user => {
getPosts(user.id, posts => {
getComments(posts[0].id, comments => {
console.log('Comments:', comments);
});
});
});
// With Promises (clean chain)
getUser(userId)
.then(user => getPosts(user.id))
.then(posts => getComments(posts[0].id))
.then(comments => {
console.log('Comments:', comments);
})
.catch(error => {
console.error('Error:', error.message);
});
// Promise-returning helpers
const getUser = (id) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ id, name: 'Alice' });
}, 100);
});
};
const getPosts = (userId) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve([{ id: 1, title: 'Post 1' }]);
}, 100);
});
};
انتشار الأخطاء
في سلاسل الـ Promise، تنتشر الأخطاء إلى أول معالج .catch().
Promise.resolve()
.then(() => {
throw new Error('Step 1 failed');
})
.then(() => {
console.log('Step 2 (skipped)');
})
.then(() => {
console.log('Step 3 (skipped)');
})
.catch(error => {
console.error('Caught error:', error.message); // 'Step 1 failed'
});
// Error recovery
Promise.reject('Network error')
.catch(error => {
console.log('Retrying...');
return 'recovered'; // New Promise with value 'recovered'
})
.then(result => {
console.log('Recovered:', result); // 'Recovered: recovered'
});
العمليات المتوازية باستخدام Promise.all()
// Fetch multiple resources in parallel
const userId = 1;
Promise.all([
fetch(`/API/users/${userId}`).then(r => r.json()),
fetch(`/API/posts/${userId}`).then(r => r.json()),
fetch(`/API/comments/${userId}`).then(r => r.json())
])
.then(([user, posts, comments]) => {
console.log('All data:', { user, posts, comments });
})
.catch(error => {
console.error('One of the requests failed:', error);
});
// Promise.race: return first completed Promise
Promise.race([
fetch('/API/data'),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 5000)
)
])
.then(response => console.log('Response received'))
.catch(error => console.error('Request timeout or failed'));
Async/Await: كود غير متزامن يبدو متزامناً
إن async/await هو "سكر برمجي" (syntactic sugar) فوق الـ Promises يتيح لك كتابة كود غير متزامن يُقرأ مثل الكود المتزامن.
أساسيات async/await
// Function declaration
async function fetchUser(id) {
// Inside async function, you can use 'await'
const response = await fetch(`/API/users/${id}`);
const user = await response.json();
return user; // Returns a Promise
}
// Arrow function
const fetchPost = async (id) => {
const response = await fetch(`/API/posts/${id}`);
return response.json();
};
// Usage
const user = await fetchUser(1);
console.log('User:', user);
// Note: await can only be used inside async functions
// (Top-level await is available in ES2022 modules)
معالجة الأخطاء باستخدام try/catch
async function fetchUserData(id) {
try {
const response = await fetch(`/API/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error('Failed to fetch user:', error.message);
throw error; // Re-throw or return fallback
} finally {
console.log('Fetch attempt completed');
}
}
// Usage
try {
const user = await fetchUserData(1);
console.log('User:', user);
} catch (error) {
console.error('Error in app:', error);
}
العمليات المتوازية باستخدام async/await
// Sequential: operations run one after another
async function fetchSequential(userId) {
const user = await fetch(`/API/users/${userId}`).then(r => r.json());
const posts = await fetch(`/API/posts/${userId}`).then(r => r.json());
const comments = await fetch(`/API/comments/${userId}`).then(r => r.json());
// Total time: sum of all requests
return { user, posts, comments };
}
// Parallel: operations start simultaneously
async function fetchParallel(userId) {
// Start all requests at once
const userPromise = fetch(`/API/users/${userId}`).then(r => r.json());
const postsPromise = fetch(`/API/posts/${userId}`).then(r => r.json());
const commentsPromise = تابع التعلم
مقالات ذات صلة
ابقَ على مسار النيرد
بريد واحد أسبوعياً — دورات، مقالات معمّقة، أدوات، وتجارب ذكاء اصطناعي.
بدون إزعاج. إلغاء الاشتراك في أي وقت.