غير متزامن هو زيادة معدل إشغال وحدة المعالجة المركزية وإبقائها مشغولة طوال الوقت.
بعض العمليات (الأكثر شيوعًا هي الإدخال/الإخراج) لا تتطلب مشاركة وحدة المعالجة المركزية وتستغرق وقتًا طويلاً للغاية. إذا لم يتم استخدام غير متزامن، فسوف تشكل حالة حظر، مما يتسبب في توقف وحدة المعالجة المركزية عن العمل وتجميد الصفحة.
عند حدوث عملية إدخال/إخراج في بيئة غير متزامنة، تضع وحدة المعالجة المركزية عمل الإدخال/الإخراج جانبًا (في هذا الوقت، يتم الاستيلاء على الإدخال/الإخراج بواسطة وحدات تحكم أخرى ولا يزال يتم نقل البيانات)، ثم تقوم بمعالجة المهمة التالية ، في انتظار اكتمال عملية الإدخال / الإخراج، قم بإخطار وحدة المعالجة المركزية (رد الاتصال هو طريقة إعلام) للعودة إلى العمل.
المحتوى الأساسي لـ "JavaScript غير المتزامن ورد الاتصال" هو أن وقت الانتهاء المحدد للعمل غير المتزامن غير مؤكد من أجل إجراء المعالجة اللاحقة بدقة بعد اكتمال العمل غير المتزامن، يجب تمرير رد الاتصال إلى الوظيفة غير المتزامنة. إكمال عملك، تابع المهام التالية.
على الرغم من أن عمليات الاسترجاعات يمكن أن تكون سهلة التنفيذ بشكل غير متزامن، إلا أنها يمكن أن تشكل جحيم رد الاتصال بسبب التداخل المتعدد. لتجنب جحيم رد الاتصال، تحتاج إلى إلغاء البرمجة المتداخلة وتغييرها إلى البرمجة الخطية.
Promise
هو الحل الأفضل للتعامل مع رد الاتصال في JavaScript
.
Promise
"وعد". يمكننا تغليف العمل غير المتزامن ونطلق عليه Promise
، أي تقديم وعد ووعد بإعطاء إشارة واضحة بعد انتهاء العمل غير المتزامن!
بناء جملة Promise
:
دع الوعد = وعد جديد(وظيفة(حل,رفض){ // عمل غير متزامن})
من خلال بناء الجملة أعلاه، يمكننا تغليف العمل غير المتزامن في Promise
. الوظيفة التي تم تمريرها عند إنشاء Promise
هي طريقة للتعامل مع العمل غير المتزامن، والمعروفة أيضًا باسم executor
.
resolve
reject
هما دالتان رد اتصال توفرهما JavaScript
نفسها ويمكن استدعاؤهما عندما يكمل executor
المهمة:
resolve(result)
- إذا تم إكمالها بنجاح، فسيتم إرجاع result
reject(error)
- إذا فشل error
و سيتم إنشاء error
؛سيتم تنفيذ executor
تلقائيًا فور إنشاء Promise
، وستغير حالة التنفيذ الخاصة به حالة الخصائص الداخلية Promise
:
state
- pending
في البداية، ثم يتم تحويلها إلى fulfilled
بعد استدعاء resolve
، أو يصبح rejected
عندما يتم استدعاء reject
result
undefined
في البداية، ثم تصبح value
بعد استدعاء resolve(value)
، أو تصبح error
بعد استدعاء reject
fs.readFile
وظيفة غير متزامنة؛ يمكننا تمريره في executor
ويتم تنفيذ عمليات قراءة الملف في الملف، وبالتالي تغليف العمل غير المتزامن.
تحتوي التعليمة البرمجية التالية على وظيفة fs.readFile
، وتستخدم resolve(data)
لمعالجة النتائج الناجحة reject(err)
لمعالجة النتائج الفاشلة.
الكود كما يلي:
Let Promise = new Promise((الحل، الرفض) => { fs.readFile('1.txt', (err, data) => { console.log('قراءة 1.txt') إذا (أخطأ) رفض (أخطأ) حل (البيانات) })})
إذا قمنا بتنفيذ هذا الكود، فسيتم إخراج الكلمات "قراءة 1.txt"، مما يثبت أن عملية قراءة الملف تتم مباشرة بعد إنشاء Promise
.
عادةً ما يقوم
Promise
بتغليف التعليمات البرمجية غير المتزامنة داخليًا، ولكنه لا يقوم فقط بتغليف التعليمات البرمجية غير المتزامنة.
تتضمن حالة Promise
أعلاه عملية قراءة الملف. سيتم قراءة الملف فورًا بعد اكتمال الإنشاء. إذا كنت ترغب في الحصول على نتيجة تنفيذ Promise
، فأنت بحاجة إلى استخدام ثلاث طرق then
، catch
، finally
.
يمكن استخدام then
Promise
للتعامل مع العمل بعد اكتمال تنفيذ Promise
، حيث تتلقى معلمتين لرد الاتصال، ويكون بناء الجملة كما يلي:
Promise.then(function(result),function(error))
النتيجة
result
المعلمة هي القيمة resolve
error
reject
؛ وعد = وعد جديد ((حل، رفض) => { fs.readFile('1.txt', (err, data) => { console.log('قراءة 1.txt') إذا (أخطأ) رفض (أخطأ) حل (البيانات) })})promise.then( (البيانات) => { console.log("تم التنفيذ بنجاح، والنتيجة هي" + data.toString()) }, (يخطئ) => { console.log ("فشل التنفيذ، الخطأ هو" + err.message) })
إذا تم تنفيذ قراءة الملف بنجاح، فسيتم استدعاء الوظيفة الأولى:
PS E:CodeNodedemos 3-callback>node .index.js قراءة 1.txt إذا تم تنفيذها بنجاح، تكون النتيجة
حذف 1.txt
إذا فشل التنفيذ، فسيتم استدعاء الوظيفة الثانية:
PS E:CodeNodedemos 3-callback>node .index.js. قراءة 1.txt فشل التنفيذ مع الخطأ ENOENT: لا يوجد مثل هذا الملف أو الدليل، افتح 'E:CodeNodedemos 3-callback1.txt'
إذا ركزنا فقط على نتيجة التنفيذ الناجح، فيمكننا تمرير ملف واحد فقط وظيفة رد الاتصال:
الوعد .ثم ((البيانات)=>{ console.log("تم التنفيذ بنجاح، والنتيجة هي" + data.toString())})
في هذه المرحلة قمنا بتنفيذ عملية قراءة غير متزامنة للملف.
إذا ركزنا فقط على نتيجة الفشل، فيمكننا تمرير null
إلى النتيجة الأولى then
رد الاتصال: promise.then(null,(err)=>{...})
.
أو استخدم طريقة أكثر أناقة: promise.catch((err)=>{...})
Let Promise = new Promise((resolve,reject) => { fs.readFile('1.txt', (err, data) => { console.log('قراءة 1.txt') إذا (أخطأ) رفض (أخطأ) حل (البيانات) })})promise.catch((يخطئ)=>{ console.log(err.message)})
.catch((err)=>{...})
then(null,(err)=>{...})
لهما نفس التأثير تمامًا.
.finally
هي دالة سيتم تنفيذها بغض النظر عن نتيجة promise
، ولها نفس الغرض الذي تستخدمه finally
في try...catch...
، ويمكنها التعامل مع العمليات التي لا علاقة لها بالنتيجة.
على سبيل المثال:
وعد جديد((حل،رفض)=>{ //شيء ما...}).finally(()=>{console.log('تنفيذ بغض النظر عن النتيجة')}).then(result=>{...}, err=>{...} )رد الاتصال أخيرًا لا يحتوي على معلمات،
fs.readFile()
10 ملفات بالتسلسل وإخراج المحتويات من الملفات العشرة على التوالي.
نظرًا لأن fs.readFile()
نفسه غير متزامن، فيجب علينا استخدام تداخل رد الاتصال كما يلي:
fs.readFile('1.txt', (err, data) => {. console.log(data.toString()) //1 fs.readFile('2.txt', (err, data) => { console.log(data.toString()) fs.readFile('3.txt', (err, data) => { console.log(data.toString()) fs.readFile('4.txt', (err, data) => { console.log(data.toString()) fs.readFile('5.txt', (err, data) => { console.log(data.toString()) fs.readFile('6.txt', (err, data) => { console.log(data.toString()) fs.readFile('7.txt', (err, data) => { console.log(data.toString()) fs.readFile('8.txt', (err, data) => { console.log(data.toString()) fs.readFile('9.txt', (err, data) => { console.log(data.toString()) fs.readFile('10.txt', (err, data) => { console.log(data.toString()) // ==> بوابة الجحيم}) }) }) }) }) }) }) }) })})
على الرغم من أن الكود أعلاه يمكنه إكمال المهمة، إلا أنه مع زيادة تداخل الاستدعاءات، يصبح مستوى الكود أعمق وتزداد صعوبة الصيانة، خاصة عندما نستخدم كودًا حقيقيًا قد يحتوي على العديد من الحلقات والعبارات الشرطية، بدلاً من console.log(...)
في المثال.
إذا لم نستخدم عمليات الاسترجاعات وقمنا باستدعاء fs.readFile()
مباشرة بالتسلسل وفقًا للتعليمة البرمجية التالية، فماذا سيحدث؟
// ملاحظة: هذه طريقة خاطئة لكتابة fs.readFile('1.txt', (err, data) => { console.log(data.toString())})fs.readFile('2.txt', (err, data) => { console.log(data.toString())})fs.readFile('3.txt', (err, data) => { console.log(data.toString())})fs.readFile('4.txt', (err, data) => { console.log(data.toString())})fs.readFile('5.txt', (err, data) => { console.log(data.toString())})fs.readFile('6.txt', (err, data) => { console.log(data.toString())})fs.readFile('7.txt', (err, data) => { console.log(data.toString())})fs.readFile('8.txt', (err, data) => { console.log(data.toString())})fs.readFile('9.txt', (err, data) => { console.log(data.toString())})fs.readFile('10.txt', (err, data) => { console.log(data.toString())})
فيما يلي نتائج الاختبار الذي أجريته (تختلف نتائج كل تنفيذ):
PS E:CodeNodedemos 3-callback> عقدة .indexالسبب أن
js12346957108
ينتج أن هذه النتيجة غير المتسلسلة غير متزامنة ، ولا يمكن تحقيق التوازي غير المتزامن في خيط واحد.
سبب استخدام حالة الخطأ هذه هنا هو التأكيد على مفهوم عدم التزامن. إذا لم تفهم سبب حدوث هذه النتيجة، فيجب عليك العودة وتعويض الدرس!
فكرة استخدام Promise
لحل قراءة الملفات المتسلسلة غير المتزامنة:
promise1
واستخدام resolve
لإرجاع النتيجة.promise1.then
لتلقي وإخراج نتيجة قراءة الملف،promise2
الجديد في promise1.then
ثم العودةpromise2.then
ثم لتلقي وإخراج نتيجة القراءةpromise3
الجديد في promise2.then
ثم العودةpromise3.then
ثم لتلقي وإخراج نتيجة... الكود كما يلي:
دع الوعد1 = الوعد الجديد((الحل، الرفض) => { fs.readFile('1.txt', (err, data) => { إذا (أخطأ) رفض (أخطأ) حل (البيانات) })})دع الوعد2 = الوعد1.ثم( البيانات => { console.log(data.toString()) إرجاع وعد جديد ((حل، رفض) => { fs.readFile('2.txt', (err, data) => { إذا (أخطأ) رفض (أخطأ) حل (البيانات) }) }) })دع الوعد3 = الوعد2.ثم( البيانات => { console.log(data.toString()) إرجاع وعد جديد ((حل، رفض) => { fs.readFile('3.txt', (err, data) => { إذا (أخطأ) رفض (أخطأ) حل (البيانات) }) }) })دع الوعد4 = الوعد3.ثم( البيانات => { console.log(data.toString()) // ..... })... ...
بهذه الطريقة نكتب جحيم رد الاتصال المتداخل الأصلي في الوضع الخطي.
ولكن لا تزال هناك مشكلة في الكود، على الرغم من أن الكود أصبح أكثر جمالا من حيث الإدارة، إلا أنه يزيد بشكل كبير من طول الكود.
الكود أعلاه طويل جدًا، يمكننا تقليل كمية الكود من خلال خطوتين:
promise
الوسيط، وربط .then
الكود كما يلي:
الدالة myReadFile (path) { إرجاع وعد جديد ((حل، رفض) => { fs.readFile(path, (err, data) => { إذا (أخطأ) رفض (أخطأ) console.log(data.toString()) حل() }) })}myReadFile('1.txt') .then(data => { return myReadFile('2.txt') }) .then(data => { return myReadFile('3.txt') }) .then(data => { return myReadFile('4.txt') }) .then(data => { return myReadFile('5.txt') }) .then(data => { return myReadFile('6.txt') }) .then(data => { return myReadFile('7.txt') }) .then(data => { return myReadFile('8.txt') }) .then(data => { return myReadFile('9.txt') }) .then(data => { return myReadFile('10.txt') })
بما أن طريقة myReadFile
ستُرجع Promise
جديدًا، يمكننا تنفيذ طريقة .then
مباشرة وتُسمى طريقة البرمجة هذه بالبرمجة المتسلسلة .
نتيجة تنفيذ التعليمات البرمجية هي كما يلي:
PS E:CodeNodedemos 3-callback> عقدة .index.js12345678910
هذا يكمل عملية قراءة الملف غير المتزامنة والمتسلسلة.
ملاحظة: يجب إرجاع كائن
Promise
جديد بالطريقة.then
لكل خطوة، وإلا فسيتم استلامPromise
القديم السابق.وذلك لأن كل طريقة
then
ستستمر في تمريرPromise
للأسفل.