برمجة غير متزامنة أنيقة مع وعود

يوم جيد يا اصدقاء!

الوعود (الوعود) هي ميزة جديدة نسبيًا لجافا سكريبت تسمح لك بتأخير تنفيذ الإجراء حتى الانتهاء من الإجراء السابق أو الرد على إجراء غير ناجح. هذا يساهم في التحديد الصحيح لتسلسل العمليات غير المتزامنة. تتناول هذه المقالة كيفية عمل الوعود ، وكيفية استخدامها في Web API ، وكيف يمكنك كتابة وعدك الخاص.

الشروط: محو الأمية الحاسوبية الأساسية ، ومعرفة أساسيات شبيبة.
الهدف: فهم ما هي الوعود وكيفية استخدامها.

ما هي الوعود؟


استعرضنا بإيجاز الوعود الواردة في المقالة الأولى من الدورة التدريبية ، وهنا سننظر فيها بمزيد من التفصيل.

في الجوهر ، الوعد هو كائن يمثل حالة وسيطة للعملية - إنه "يعد" بأن النتيجة سيتم إرجاعها في المستقبل. لا يُعرف بالضبط متى سيتم الانتهاء من العملية ومتى سيتم إرجاع النتيجة ، ولكن هناك ضمان بأنه عند اكتمال العملية ، فإن التعليمات البرمجية الخاصة بك إما ستفعل شيئًا مع النتيجة أو ستتعامل مع الخطأ بأمان.

كقاعدة ، كم من الوقت تستغرق عملية غير متزامنة (ليست طويلة جدًا!) أقل اهتمامًا بنا من القدرة على الاستجابة فورًا لإنجازها. وبالطبع ، من الجيد أن تعرف أن باقي الكود غير محجوب.

واحدة من أكثر الوعود شيوعًا هي واجهات برمجة تطبيقات الويب التي تُرجع الوعود. دعونا نلقي نظرة على تطبيق دردشة فيديو افتراضي. يحتوي التطبيق على نافذة بها قائمة بأصدقاء المستخدم ، والنقر على الزر الموجود بجوار اسم (أو الصورة الرمزية) للمستخدم يبدأ مكالمة فيديو.

المكالمات معالج الزر getUserMedia ()للوصول إلى كاميرا المستخدم والميكروفون. من اللحظة ، يتصل getUserMedia () بالمستخدم للحصول على إذن (لاستخدام الأجهزة ، أي جهاز يستخدمه إذا كان لدى المستخدم عدة ميكروفونات أو كاميرات ، فقط مكالمة صوتية ، من بين أشياء أخرى) ، يتوقع getUserMedia () ليس فقط قرار المستخدم ، ولكن أيضًا الأجهزة إذا كانت قيد الاستخدام حاليًا. بالإضافة إلى ذلك ، قد لا يستجيب المستخدم على الفور. كل هذا يمكن أن يؤدي إلى تأخيرات كبيرة في الوقت المناسب.

إذا تم تقديم طلب للحصول على إذن لاستخدام الأجهزة من سلسلة المحادثات الرئيسية ، فسيتم حظر المتصفح حتى اكتمال getUserMedia (). هذا غير مقبول. بدون وعود ، يصبح كل شيء في المتصفح "غير قابل للنقر" حتى يمنح المستخدم الإذن لاستخدام الميكروفون والكاميرا. لذلك ، بدلاً من انتظار قرار المستخدم وإعادة MediaStream لدفق تم إنشاؤه من مصادر (الكاميرا والميكروفون) ، يعيد getUserMedia () وعدًا بأن MediaStream يعالج بمجرد توفره.

قد يبدو رمز تطبيق محادثة الفيديو كما يلي:

function handle CallButton(evt){
    setStatusMessage('Calling...')
    navigator.mediaDevices.getUserMedia({video: true, audio: true})
    .then(chatStream => {
        selfViewElem.srcObject = chatStream
        chatStream.getTracks().forEach(track => myPeerConnection.addTrack(track, chatStream))
        setStatusMessage('Connected')
    }).catch(err => {
        setStatusMessage('Failed to connect')
    })
}

تبدأ الوظيفة باستدعاء setStatusMessage () ، وتعرض الرسالة "جارٍ الاتصال ..." ، والتي تعمل كمؤشر على محاولة إجراء مكالمة. ثم يتم استدعاء getUserMedia () ، طلب دفق يحتوي على مقاطع الفيديو والصوت. بمجرد تكوين الدفق ، يتم تثبيت عنصر فيديو لعرض الدفق من الكاميرا يسمى "العرض الذاتي" ، تتم إضافة المسارات الصوتية إلى WebRTC RTCPeerConnection ، وهو اتصال بمستخدم آخر. بعد ذلك ، يتم تحديث الحالة إلى "متصل".

إذا فشل getUserMedia () ، يتم تشغيل كتلة الالتقاط. يستخدم setStatusMessage () لعرض رسالة خطأ.

لاحظ أنه يتم إرجاع المكالمة إلى getUserMedia () حتى إذا لم يتم تلقي دفق الفيديو حتى الآن. حتى إذا أعادت الدالة handleCallButton () التحكم إلى التعليمات البرمجية التي استدعته ، بمجرد الانتهاء من تنفيذ getUserMedia () ، فستستدعي المعالج. إلى أن "يفهم" التطبيق أن البث قد بدأ ، سيكون getUserMedia () في وضع الاستعداد.

ملاحظة: يمكنك معرفة المزيد عن هذا في مقال "الإشارات ومكالمات الفيديو" . توفر هذه المقالة كودًا أكثر اكتمالًا من الذي استخدمناه في المثال.

مشكلة وظائف رد الاتصال


لفهم سبب كون الوعود حلاً جيدًا ، من المفيد النظر إلى الطريقة القديمة لكتابة الاستدعاءات لمعرفة ما هي مشاكلهم.

كمثال ، فكر في طلب البيتزا. يتكون طلب البيتزا الناجح من عدة خطوات يجب إجراؤها بالترتيب ، واحدة تلو الأخرى:

  1. اختر الحشوة. قد يستغرق ذلك بعض الوقت إذا فكرت في الأمر لفترة طويلة ، وفشل إذا غيرت رأيك وطلبت الكاري.
  2. نضع طلبية. يستغرق طهي البيتزا بعض الوقت وقد يفشل إذا كان المطعم يفتقر إلى المكونات الضرورية.
  3. نحصل على البيتزا ونأكل. قد يفشل استلام البيتزا إذا لم نتمكن ، على سبيل المثال ، من دفع ثمن الطلب.

قد يبدو الرمز الزائف القديم باستخدام وظائف رد الاتصال كما يلي:

chooseToppings(function(toppings){
    placeOrder(toppings, function(order){
        collectOrder(order, function(pizza){
            eatPizza(pizza)
        }, failureCallback)
    }, failureCallback)
}, failureCallback)

من الصعب قراءة هذه التعليمات البرمجية وصيانتها (غالبًا ما يطلق عليها "جحيم رد الاتصال" أو "جحيم رد الاتصال"). يجب استدعاء دالة failureCallback () عند كل مستوى تداخل. هناك مشاكل أخرى.

نستخدم الوعود


الوعود تجعل الشفرة أنظف وأسهل في الفهم والدعم. إذا أعدنا كتابة الشفرة الكاذبة باستخدام وعود غير متزامنة ، نحصل على ما يلي:

chooseToppings()
.then(function(toppings){
    return placeOrder(toppings)
})
.then(function(order){
    return collectOrder(order)
})
.then(function(pizza){
    eatPizza(pizza)
})
.catch(failureCallback)

هذا أفضل بكثير - نرى ما يحدث ، نستخدم كتلة .catch () واحدة للتعامل مع جميع الأخطاء ، ولا تحجب الوظيفة التدفق الرئيسي (حتى نتمكن من لعب ألعاب الفيديو أثناء انتظار البيتزا) ، ويضمن تنفيذ كل عملية بعد اكتمال العملية السابقة. نظرًا لأن كل وعد يُرجع وعدًا ، يمكننا استخدام السلسلة .then. عظيم ، صحيح؟

باستخدام وظائف الأسهم ، يمكن تبسيط الشفرة الزائفة:

chooseToppings()
.then(toppings =>
    placeOrder(toppings)
)
.then(order =>
    collectOrder(order)
)
.then(pizza =>
    eatPizza(pizza)     
)
.catch(failureCallback)

أو حتى هكذا:

chooseToppings()
.then(toppings => placeOrder(toppings))
.then(order => collectOrder(order))
.then(pizza => eatPizza(pizza))
.catch(failureCallback)

يعمل هذا لأن () => x مطابق لـ () => {return x}.

يمكنك حتى القيام بذلك (نظرًا لأن الدوال ببساطة تمرر المعلمات ، لا نحتاج إلى طبقات):

chooseToppings().then(placeOrder).then(collectOrder).then(eatPizza).catch(failureCallback)

هذه الشفرة أكثر صعوبة في القراءة ولا يمكن استخدامها مع بنيات أكثر تعقيدًا من الرمز الزائف.

ملاحظة: لا يزال من الممكن تحسين الشفرة الزائفة باستخدام المتزامن / الانتظار ، والذي سيتم مناقشته في مقال لاحق.

في جوهرها ، تشبه الوعود مستمعي الأحداث ، ولكن مع بعض الاختلافات:

  • الوعد يتحقق مرة واحدة فقط (النجاح أو الفشل). لا يمكن تنفيذه مرتين أو التبديل من النجاح إلى الفشل أو العكس بعد اكتمال العملية.
  • إذا اكتمل الوعد ، وأضفنا وظيفة رد اتصال لمعالجة نتائجه ، فسيتم استدعاء هذه الوظيفة ، على الرغم من وقوع الحدث قبل إضافة الوظيفة.

بناء الوعد الأساسي: مثال حقيقي


إن معرفة الوعود أمر مهم لأن العديد من واجهات برمجة تطبيقات الويب تستخدمها في الوظائف التي تؤدي مهام معقدة. يتضمن العمل باستخدام تقنيات الويب الحديثة استخدام الوعود. سنتعلم لاحقًا كيفية كتابة وعودنا الخاصة ، ولكن الآن ، لنلقِ نظرة على بعض الأمثلة البسيطة التي يمكن العثور عليها في Web API.

في المثال الأول ، نستخدم طريقة fetch () للحصول على الصورة من الشبكة ، وأسلوب blob () لتحويل محتويات نص الاستجابة إلى كائن Blob وعرض هذا الكائن داخل عنصر <img> . هذا المثال مشابه جدًا للمثال من المقال الأول ، لكننا سنفعل ذلك بشكل مختلف قليلاً.

ملاحظة: لن يعمل المثال التالي إذا قمت ببساطة بتشغيله من ملف (أي باستخدام ملف: // URL). تحتاج إلى تشغيله من خلال خادم محلي أو استخدام حلول عبر الإنترنت مثل صفحات Glitch أو GitHub .

1. أولا وقبل كل شيء، تحميل HTML و الصورة التي سوف تتلقاها.

2. أضف عنصر <script> إلى نهاية <body> .

3. داخل عنصر <script> ، أضف السطر التالي:

let promise = fetch('coffee.jpg')

يتم استخدام طريقة الجلب () ، التي تأخذ عنوان URL للصورة كمعلمة ، للحصول على الصورة من الشبكة. كمعلمة ثانية ، يمكننا تحديد كائن بإعدادات ، ولكن في الوقت الحالي سنقتصر على خيار بسيط. يتم تخزين الوعد الذي تم إرجاعه بواسطة الجلب () في متغير الوعد. كما أشرنا سابقًا ، الوعد هو كائن يمثل حالة وسيطة - الاسم الرسمي لدولة معينة معلّق.

4. للعمل مع نتيجة الإكمال الناجح للوعود (في حالتنا ، عند عودة الاستجابة ) ، نسمي طريقة .then (). يتم تشغيل وظيفة رد الاتصال داخل كتلة .then () (يشار إليها غالبًا باسم المنفذ) فقط عندما يكتمل الوعد بنجاح ويتم إرجاع كائن الاستجابة - يقولون أن الوعد قد اكتمل (تم الوفاء به ، وملء). يتم تمرير الاستجابة كمعلمة.

ملحوظة. يشبه تشغيل كتلة .then () عملية مستمع الأحداث AddEventListener (). يتم تشغيله فقط بعد وقوع الحدث (بعد الوفاء بالوعد). الفرق بين الاثنين هو أنه يمكن استدعاء .then () مرة واحدة فقط ، بينما تم تصميم المستمع لمكالمات متعددة.

بعد تلقي الاستجابة ، نسمي طريقة blob () لتحويل الاستجابة إلى كائن Blob. تبدو هكذا:

response => response.blob()

... وهو دخول قصير لـ:

function (response){
    return response.blob()
}

حسنا ، كلمات كافية. يضاف ما يلي بعد السطر الأول:

let promise2 = promise.then(response => response.blob())

5. كل دعوة إلى .then () تخلق وعدًا جديدًا. هذا مفيد جدًا: نظرًا لأن blob () يُرجع أيضًا وعدًا ، يمكننا معالجة كائن Blob عن طريق استدعاء .then () في الوعد الثاني. نظرًا لأننا نريد القيام بشيء أكثر تعقيدًا من استدعاء الطريقة وإرجاع النتيجة ، نحتاج إلى لف نص الدالة بأقواس متعرجة (وإلا سيتم طرح استثناء):

أضف ما يلي إلى نهاية الرمز:

let promise3 = promise2.then(myBlob => {

})

6. دعنا نملأ نص الوظيفة التنفيذية. أضف الأسطر التالية إلى الأقواس:

let objectURL = URL.createObjectURL(myBlob)
let image = document.createElement('img')
image.src = objectURL
document.body.appendChild(image)

هنا نسمي طريقة URL.createObjectURL () ، لتمريرها كمعلمة Blob ، والتي أعادت الوعد الثاني. نحصل على رابط للكائن. ثم نقوم بإنشاء عنصر <img> ، ونعين سمة src بقيمة ارتباط الكائن ونضيفه إلى DOM بحيث تظهر الصورة على الصفحة.

إذا قمت بحفظ HTML وتحميله في متصفح ، سترى أن الصورة معروضة كما هو متوقع. عمل عظيم!

ملحوظة. ربما تكون قد لاحظت أن هذه الأمثلة مقلقة إلى حد ما. يمكنك الاستغناء عن طرق الجلب () و blob () وتعيين <img> عنوان URL المقابل ، coffee.jpg. ذهبنا بهذه الطريقة لإثبات العمل مع الوعود بمثال بسيط.

استجابة الفشل


نسينا شيئًا - ليس لدينا معالج خطأ في حالة فشل أحد الوعود (سيتم رفضه). يمكننا إضافة معالج خطأ باستخدام طريقة .catch (). لنفعل ذلك:

let errorCase = promise3.catch(e => {
    console.log('There has been a problem with your fetch operation: ' + e.message)
})

لمشاهدة هذا بشكل عملي ، حدد عنوان URL غير الصحيح للصورة وأعد تحميل الصفحة. ستظهر رسالة خطأ في وحدة التحكم.

في هذه الحالة ، إذا لم نقم بإضافة كتلة .atch () ، فسيتم عرض رسالة خطأ أيضًا في وحدة التحكم. ومع ذلك ، يسمح لنا .catch () بمعالجة الأخطاء بالطريقة التي نريدها. في تطبيق حقيقي ، قد تحاول كتلة .catch () استعادة الصورة أو عرض الصورة الافتراضية ، أو تطلب من المستخدم تحديد صورة أخرى أو القيام بشيء آخر.

ملحوظة. شاهد عرضًا توضيحيًا مباشرًا ( شفرة المصدر ).

دمج الكتلة


في الواقع ، الطريقة التي استخدمناها لكتابة الكود ليست مثالية. لقد ذهبنا عمدا بهذه الطريقة حتى تتمكن من فهم ما يحدث في كل مرحلة. كما هو موضح سابقًا ، يمكننا دمج كتل .then () (و .catch () block). يمكن إعادة كتابة الكود الخاص بنا على النحو التالي (انظر أيضًا simple-fetch-chained.html على GitHub):

fetch('coffee.jpg')
.then(response => response.blob())
.then(myBlob => {
    let objectURL = URL.createObjectURL(myBlob)
    let image = document.createElement('img')
    image.src = objectURL
    document.body.appendChild(image)
})
.catch(e => {
    console.log('There has been a problem with your fetch operation: ' + e.message)
})

تذكر أن القيمة التي يتم إرجاعها بواسطة الوعد يتم تمريرها كمعلمة إلى الوظيفة التالية المنفذة للكتلة .then ().

ملحوظة. الكتل .then () /. Catch () في الوعود هي معادلات غير متزامنة لكتلة try ... catch المتزامنة. تذكر: المحاولة المتزامنة ... لن يعمل الالتقاط في التعليمات البرمجية غير المتزامنة.

استنتاجات الوعد المصطلحات


دعونا نقيم ونكتب دليلاً موجزًا ​​يمكنك استخدامه في المستقبل. لتعزيز المعرفة ، نوصي بقراءة القسم السابق عدة مرات.

1. عندما يتم إنشاء الوعد ، يقولون أنه في حالة توقع.
2- عند رجوع الوعد يقولون إنه تم (حل):

  1. 1. يسمى الوعد المكتمل بنجاح. تقوم بإرجاع القيمة التي يمكن الحصول عليها من خلال السلسلة من .then () في نهاية الوعد. تحتوي وظيفة التنفيذ في كتلة .then () على القيمة التي أرجعها الوعد.
  2. 2. يسمى الوعد الذي لم ينجح بنجاح برفض. تعيد السبب ، رسالة الخطأ التي أدت إلى رفض الوعد. يمكن الحصول على هذا السبب من خلال كتلة .atch () في نهاية الوعد.

قم بتشغيل التعليمات البرمجية بعد عدة وعود


تعلمنا أساسيات استخدام الوعود. الآن دعونا نلقي نظرة على ميزات أكثر تقدمًا. السلسلة من .then () جيدة ، ولكن ماذا لو أردنا استدعاء عدة كتل من الوعود واحدة تلو الأخرى؟

يمكنك القيام بذلك باستخدام طريقة Promise.all () القياسية. تأخذ هذه الطريقة مجموعة من الوعود كمعلمة وتعيد وعدًا جديدًا عندما يتم الوفاء بجميع الوعود في الصفيف. يبدو شيء من هذا القبيل:

Promise.all([a,b,c]).then(values => {
        ...
})

إذا تم تنفيذ جميع الوعود ، فسيتم تمرير صفيف منفذ كتلة .then () كمعلمة. إذا لم يتم الوفاء بوعود واحدة على الأقل ، فسيتم رفض الكتلة بالكامل.

هذا يمكن أن يكون مفيدا للغاية. تخيل أننا نتلقى معلومات لملء واجهة المستخدم ديناميكيًا بالمحتوى الموجود على صفحتنا. في كثير من الحالات ، يكون من المنطقي تلقي جميع المعلومات وعرضها على الصفحة في وقت لاحق عن عرض المعلومات في أجزاء.

دعنا نلقي نظرة على مثال آخر:
1. قم بتنزيل قالب صفحة ووضع علامة <script> قبل علامة الإغلاق </body>.

2. تحميل الملفات المصدر ( coffee.jpg ، tea.jpg و description.txt) أو استبدالها بك.

3. في البرنامج النصي ، نحدد أولاً دالة ترجع الوعود التي نمررها إلى Promise.all (). سيكون من السهل القيام بذلك إذا قمنا بتشغيل Promise.all () بعد إكمال عمليات الجلب () الثلاثة. يمكننا القيام بما يلي:

let a = fetch(url1)
let b = fetch(url2)
let c = fetch(url3)

Promise.all([a, b, c]).then(values => {
    ...
})

عند تنفيذ الوعد ، سيحتوي متغير "القيم" على ثلاثة كائنات استجابة ، واحد من كل عملية إحضار () مكتملة.

ومع ذلك ، نحن لا نريد هذا. لا يهمنا عند اكتمال عمليات الجلب (). ما نريده حقًا هو البيانات المحملة. هذا يعني أننا نريد تشغيل كتلة Promise.all () بعد تلقي النقطة الصحيحة التي تمثل الصور والسلاسل النصية الصالحة. يمكننا كتابة دالة تقوم بذلك ؛ أضف ما يلي إلى عنصر <script> الخاص بك:

function fetchAndDecode(url, type){
    return fetch(url).then(response => {
        if(type === 'blob'){
            return response.blob()
        } else if(type === 'text'){
            return response.text()
        }
    }).catch(e => {
        console.log('There has been a problem with your fetch operation ' + e.message)
    })
}

يبدو الأمر معقدًا بعض الشيء ، لذا دعنا

ننتقل إلى التعليمات البرمجية خطوة بخطوة: 1. أولاً وقبل كل شيء ، نعلن عن وظيفة ونمررها عنوان URL ونوع الملف الناتج.

2. هيكل الوظيفة مشابه لما رأيناه في المثال الأول - نسمي وظيفة fetch () للحصول على الملف على عنوان URL معين ثم نقل الملف إلى وعد آخر يعيد نص الاستجابة الذي تم فك ترميزه (قراءة). في المثال السابق ، كانت دائمًا طريقة blob ().

3. هناك اختلافان:

  • أولاً ، يعتمد وعد العودة الثاني على نوع القيمة. داخل وظيفة التنفيذ ، نستخدم عامل التشغيل if ... آخر إذا كان لإرجاع الوعد بناءً على نوع الملف الذي نحتاج إلى فك تشفيره (في هذه الحالة ، نختار بين blob والنص ، ولكن يمكن توسيع المثال بسهولة للعمل مع أنواع أخرى).
  • -, «return» fetch(). , (.. , blob() text(), , , ). , return .

4. في النهاية ، نسمي طريقة .catch () لمعالجة أي أخطاء قد تحدث في الوعود في الصفيف الذي تم تمريره إلى .all (). إذا تم رفض أحد الوعود ، فسوف تتيح لنا كتلة الالتقاط معرفة الوعد الذي كانت المشكلة به. سيستمر تنفيذ الحظر .all () ، لكنه لن يعرض النتائج التي حدثت بها أخطاء. إذا كنت تريد رفض .all () ، فأضف كتلة .catch () في النهاية.

الكود الموجود داخل الوظيفة غير متزامن ويستند إلى الوعود ، لذلك تعمل الوظيفة بأكملها مثل الوعد.

4. بعد ذلك ، نسمي وظيفتنا ثلاث مرات لبدء عملية استقبال وفك تشفير الصور والنصوص ، ووضع كل من الوعود في متغير. يضاف ما يلي:

let coffee = fetchAndDecode('coffee.jpg', 'blob')
let tea = fetchAndDecode('tea.jpg', 'blob')
let description = fetchAndDecode('description.txt', 'text')

5. بعد ذلك ، نعلن عن كتلة Promise.all () لتنفيذ بعض التعليمات البرمجية فقط بعد الوفاء بالوعود الثلاثة. إضافة كتلة بوظيفة منفذ فارغة داخل .then ():

Promise.all([coffee, tea, description]).then(values => {

})

يمكنك أن ترى أن الأمر يتطلب مجموعة من الوعود كمعلمة. سيبدأ المقاول فقط بعد الوفاء بالوعود الثلاثة ؛ عندما يحدث ذلك ، سيتم وضع نتيجة كل وعد (نص الاستجابة الذي تم فك ترميزه) في مصفوفة ، ويمكن تمثيل ذلك على أنه [نتائج قهوة ، نتائج شاي ، نتائج وصف].

6. أخيرًا ، أضف ما يلي إلى المنفذ (هنا نستخدم رمزًا متزامنًا بسيطًا إلى حد ما لوضع النتائج في المتغيرات (إنشاء كائنات URL من النقطة) وعرض الصور والنص على الصفحة):

console.log(values)
//       
let objectURL1 = URL.createObjectURL(values[0])
let objectURL2 = URL.createObjectURL(values[1])
let descText = values[2]

//  
let image1 = document.createElement('img')
let image2 = document.createElement('img')
image1.src = objectURL1
image2.src = objectURL2
document.body.appendChild(image1)
document.body.appendChild(image2)

//  
let para = document.createElement('p')
para.textContent = descText
document.body.appendChild(para)

احفظ التغييرات وأعد تحميل الصفحة. يجب أن ترى أن جميع مكونات واجهة المستخدم يتم تحميلها ، على الرغم من أن هذا لا يبدو جذابًا للغاية.

ملحوظة. إذا كان لديك أي صعوبات، يمكنك مقارنة إصدار قانون مع مصالحنا - هنا هو الحي تجريبي و شفرة المصدر .

ملحوظة. من أجل تحسين هذا الرمز ، يمكنك التكرار من خلال العناصر المعروضة ، وتلقي كل منها وفك تشفيرها ، ثم التكرار من خلال النتائج داخل Promise.all () ، وتشغيل وظائف مختلفة لعرض كل نتيجة اعتمادًا على نوعها. سيسمح لك ذلك بالعمل مع أي عدد من العناصر.

بالإضافة إلى ذلك ، يمكنك تحديد نوع الملف الناتج دون الحاجة إلى تحديد خاصية النوع بشكل صريح. هذا ، على سبيل المثال ، يمكن القيام بهresponse.headers.get ("content-type") للتحقق من رأس نوع محتوى بروتوكول HTTP.

قم بتشغيل الكود بعد تنفيذ / رفض الوعد


غالبًا ، قد تحتاج إلى تنفيذ التعليمات البرمجية بعد إتمام الوعد ، بغض النظر عما إذا تم تنفيذه أو رفضه. في السابق ، كان علينا تضمين نفس الكود في كل من كتلة .then () وكتلة .catch () ، على سبيل المثال:

myPromise
.then(response => {
    doSomething(response)
    runFinalCode()
})
.catch(e => {
    returnError(e)
    runFinalCode()
})

في المتصفحات الحديثة ، تتوفر طريقة .finally () ، والتي تتيح لك تشغيل الكود بعد اكتمال الوعد ، مما يتجنب التكرار ويجعل الكود أكثر أناقة. يمكن إعادة كتابة كود المثال السابق كما يلي:

myPromise
.then(response => {
    doSomething(response)
})
.catch(e => {
    returnError(e)
})
.finally(() => {
    runFinalCode()
})

يمكنك أن ترى استخدام هذا النهج مع مثال حقيقي - العرض التوضيحي المباشر - أخيرا. html ( شفرة المصدر ). وهي تعمل مثل Promise.all () من المثال السابق ، باستثناء أننا نضيف أخيرًا () إلى نهاية السلسلة في دالة fetchAndDecode ():

function fetchAndDecode(url, type){
    return fetch(url).then(response => {
        if(type === 'blob'){
            return response.blob()
        } else if(type === 'text'){
            return response.text()
        }
    }).catch(e => {
        console.log(`There has been a problem with your fetch operation for resource "${url}": ${e.message}`)
    }).finally(() => {
        console.log(`fetch attempt for "${url}" finished.`)
    })
}

سوف نتلقى رسالة حول إتمام كل محاولة للحصول على الملف.

ملحوظة. ثم () / catch () / وأخيرا () هو المكافئ غير المتزامن لـ try () / catch () / وأخيرا ().

كتابة وعدك الخاص


الخبر السار هو أنك تعرف بالفعل كيفية القيام بذلك. عندما تقوم بدمج وعود متعددة مع .then () ، أو دمجها لتوفير وظائف محددة ، فإنك تقوم بإنشاء وظائفك غير المتزامنة والمستندة إلى الوعود. خذ على سبيل المثال دالة fetchAndDecode () من المثال السابق.

يعد الجمع بين مختلف واجهات برمجة التطبيقات التي تستند إلى الوعد لإنشاء وظائف محددة هي الطريقة الأكثر شيوعًا للعمل مع الوعود ، والتي تظهر مرونة وقوة واجهات برمجة التطبيقات الحديثة. ومع ذلك، هناك طريقة اخرى.

وعد المنشئ ()


يمكنك إنشاء وعدك الخاص باستخدام مُنشئ Promise (). قد يكون هذا ضروريًا في المواقف التي يكون لديك فيها رمز من واجهة برمجة التطبيقات غير المتزامنة القديمة التي تحتاجها "كبيرة الحجم". يتم ذلك من أجل أن تكون قادرًا على العمل في نفس الوقت مع التعليمات البرمجية القديمة والجديدة ، والمكتبات أو "الأطر" ، ومع رمز جديد قائم على الوعد.

دعونا نلقي نظرة على مثال بسيط - هنا نلف استدعاء setTimeout () في وعد - سيبدأ هذا الوظيفة في ثانيتين ، وهو ما سيفي بالوعد (باستخدام المكالمة Resolution () التي تم تمريرها) بسلسلة "Success!".

let timeoutPromise = new Promise((resolve, reject) => {
    setTimeout(function(){
        resolve('Success!')
    }, 2000)
})

حل () ورفض () هي وظائف يتم استدعاؤها لتحقيق أو رفض وعد جديد. في هذه الحالة ، يتم تحقيق الوعد بسلسلة "النجاح!".

عند استدعاء هذا الوعد ، يمكنك إضافة كتلة .then () إليه لمزيد من العمل مع سطر "Success!". حتى نتمكن من إخراج سطر في الرسالة:

timeoutPromise
.then((message) => {
    alert(message)
})

... أو هكذا:

timeoutPromise.then(alert)

شاهد عرضًا توضيحيًا مباشرًا ( شفرة المصدر ).

المثال المعين ليس مرنًا جدًا - لا يمكن الوفاء بالوعد إلا بخط بسيط ، ليس لدينا معالج خطأ - رفض () (في الواقع لا يحتاج setTimeout () إلى معالج خطأ ، لذلك في هذه الحالة لا يهم).

ملحوظة. لماذا حل () بدلاً من الوفاء ()؟ في الوقت الحالي ، الجواب هو: من الصعب شرح ذلك.

نحن نعمل برفض الوعد


يمكننا إنشاء وعد مرفوض باستخدام طريقة () () مثل تمامًا (()) ، (()) () يرفض قيمة بسيطة ، ولكن على عكس (()) ، فإن القيمة البسيطة ليست هي النتيجة ، ولكن سبب الرفض ، أي سبب الرفض. خطأ تم تمريره إلى كتلة .catch ().

دعنا نوسع المثال السابق بإضافة شرط لرفض الوعد ، بالإضافة إلى القدرة على تلقي رسائل أخرى غير رسالة النجاح.

خذ المثال السابق وأعد كتابته كما يلي:

function timeoutPromise(message, interval){
    return new Promise((resolve, reject) => {
        if(message === '' || typeof message !== 'string'){
            reject('Message is empty or not a string')
        } else if(interval < 0 || typeof interval !== number){
            reject('Interval is negative or not a number')
        } else{
            setTimeout(function(){
                resolve(message)
            }, interval)
        }
    })
}

نمرر هنا حجتين إلى الدالة - الرسالة والفاصل الزمني (مهلة زمنية). في دالة ، نعيد كائن وعد.

في مُنشئ Promise ، نجري عدة فحوصات باستخدام هياكل if ... else:

  1. أولاً ، نتحقق من الرسالة. إذا كانت فارغة أو ليست سلسلة ، فإننا نرفض الوعد ونبلغ عن خطأ.
  2. ثانيًا ، نتحقق من التأخير الزمني. إذا كان سلبيًا أو ليس رقمًا ، فإننا نرفض أيضًا الوعد ونبلغ عن خطأ.
  3. أخيرًا ، إذا كانت كلتا الوسيطتين على ما يرام ، فقم بعرض الرسالة بعد وقت معين (فاصل زمني) باستخدام setTimeout ().

نظرًا لأن timeoutPromise () تُرجع وعدًا ، يمكننا إضافة .then () و. catch () وما إلى ذلك لتحسين وظائفها. لنفعل ذلك:

timeoutPromise('Hello there!', 1000)
.then(message => {
    alert(message)
}).catch(e => {
    console.log('Error: ' + e)
})

بعد حفظ التغييرات وتشغيل التعليمات البرمجية ، ستظهر لك رسالة بعد ثانية واحدة. حاول الآن تمرير سلسلة فارغة كرسالة أو رقم سالب كفاصل زمني. سترى رسالة خطأ نتيجة لرفض الوعد.

ملحوظة. يمكنك العثور على نسختنا من هذا المثال على GitHub - custom-prom2.html ( المصدر ).

مثال أكثر واقعية.


المثال الذي قمنا بدراسته يجعل من السهل فهم المفهوم ، ولكنه ليس متزامنًا حقًا. تتم محاكاة عدم التزامن في المثال باستخدام setTimeout () ، على الرغم من أنه لا يزال يُظهر فائدة الوعود لإنشاء وظائف مع سير عمل معقول ، ومعالجة أخطاء جيدة ، وما إلى ذلك.

أحد الأمثلة على التطبيقات غير المتزامنة المفيدة التي تستخدم مُنشئ Promise () هي مكتبة idb (IndexedDB مع سهولة الاستخدام)جيك أرشيبالد. تتيح لك هذه المكتبة استخدام IndexedDB API (استنادًا إلى وظائف رد اتصال API لتخزين واسترداد البيانات من جانب العميل) ، المكتوبة على الطراز القديم ، مع الوعود. إذا نظرت إلى ملف المكتبة الرئيسي ، سترى أنه يستخدم نفس التقنيات التي درسناها أعلاه. يعمل الكود التالي على تحويل نموذج الاستعلام الأساسي الذي تستخدمه العديد من أساليب IndexedDB إلى نموذج متوافق مع الوعد:

function promisifyRequest(request){
    return new Promise(function(resolve, reject){
        request.onsuccess = function(){
            resolve(request.result)
        }

        request.onerror = function(){
            reject(request.error)
        }
    })
}

وهذا يضيف اثنين من معالجات الأحداث التي تفي بالوعد أو ترفضه ، حسب الاقتضاء:

  • عند نجاح الطلب ، يقوم معالج onsuccess بالوفاء بالوعد الناتج عن الطلب.
  • عندما يفشل الطلب ، يرفض معالج onerror الوعد بخطأ الطلب.

استنتاج


تعد الوعود طريقة رائعة لإنشاء تطبيقات غير متزامنة عندما لا نعرف القيمة التي ستعود بها الوظيفة أو المدة التي ستستغرقها. تسمح لك بالعمل مع سلسلة من العمليات غير المتزامنة دون استخدام وظائف رد الاتصال المتداخلة بشدة ، وهي تدعم نمط معالجة الأخطاء في عبارة try ... catch المتزامنة.

الوعود مدعومة من قبل جميع المتصفحات الحديثة. الاستثناء الوحيد هو Opera Mini و IE11 وإصداراته السابقة.

في هذه المقالة ، نظرنا بعيدًا عن جميع ميزات الوعود ، فقط الأكثر إثارة للاهتمام والمفيدة. ستتعرف على ميزات وتقنيات أخرى إذا كنت تريد معرفة المزيد عن الوعود.

تعتمد معظم واجهات برمجة التطبيقات الحديثة على الويب على الوعود ، لذلك يجب أن تكون الوعود معروفة. من بين واجهات برمجة تطبيقات الويب هذه ، يمكننا تسمية WebRTC و Web Audio API و Media Capture و Streams وما إلى ذلك. ستصبح الوعود أكثر شيوعًا ، لذا فإن دراستها وفهمها خطوة مهمة نحو إتقان JavaScript الحديث.

انظر أيضًا "الأخطاء الشائعة في جافا سكريبت التي تعد الجميع بمعرفتها . "

شكرآ لك على أهتمامك.

النقد البناء مرحب به.

All Articles