برمجة غير متزامنة مع عدم التزامن / انتظار

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

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

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

غير متزامن / في انتظار الأساسيات


استخدام المتزامن / انتظار جزئين.

الكلمة غير المتزامنة


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

حاول كتابة ما يلي في وحدة تحكم المستعرض:

function hello(){ return 'Hello' }
hello()

ستقوم الوظيفة بإرجاع "Hello". لا شيء غير عادي ، أليس كذلك؟

ولكن ماذا لو حولناه إلى وظيفة غير متزامنة؟ حاول القيام بما يلي:

async function hello(){ return 'Hello' }
hello()

الآن استدعاء دالة إرجاع وعد. هذه إحدى ميزات الوظائف غير المتزامنة - فهي تُرجع قيمًا مضمونة ليتم تحويلها إلى وعود.

يمكنك أيضًا إنشاء تعبيرات وظيفية غير متزامنة ، مثل:

let hello = async function(){ return hello() }
hello()

يمكنك أيضًا استخدام وظائف الأسهم:

let hello = async () => { return 'Hello' }

كل هذه الوظائف تفعل نفس الشيء.

للحصول على قيمة الوعد المكتمل ، يمكننا استخدام كتلة .then ():

hello().then((value) => console.log(value))

... أو حتى مع ذلك:

hello().then(console.log)

وبالتالي ، تؤدي إضافة الكلمة الأساسية غير المتزامنة إلى قيام الدالة بإرجاع الوعد بدلاً من القيمة. بالإضافة إلى ذلك ، فإنه يسمح بالوظائف المتزامنة لتجنب أي حمل مرتبط بتشغيل ودعم استخدام الانتظار. تتيح إضافة بسيطة غير متزامنة أمام الوظيفة تحسين الشفرة تلقائيًا بواسطة محرك JS. رائع!

الكلمة الرئيسية تنتظر


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

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

هنا مثال تافه:

async function hello(){
    return greeting = await Promise.resolve('Hello')
}

hello().then(alert)

بالطبع ، الكود أعلاه غير مجدي ، فهو يعمل فقط كدليل على بناء الجملة. دعنا ننتقل ونلقي نظرة على مثال حقيقي.

إعادة كتابة كود الوعود باستخدام غير متزامن / انتظار


خذ مثال الجلب من المقالة السابقة:

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)
})

يجب أن يكون لديك بالفعل فهم لما هي الوعود وكيف تعمل ، لكن دعنا نعيد كتابة هذا الرمز باستخدام async / انتظار لمعرفة مدى البساطة:

async function myFetch(){
    let response = await fetch('coffee.jpg')
    let myBlob = await response.blob()

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

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

وهذا يجعل الشفرة أبسط وأسهل في الفهم - لا توجد كتل .then ()!

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

async function myFetch(){
    let response = await fetch('coffee.jpg')
    return await response.blob()
}

myFetch().then((blob) => {
    let objectURL = URL.createObjectURL(blob)
    let image = document.createElement('image')
    image.src = objectURL
    document.body.appendChild(image)
}).catch(e => console.log(e))

يمكنك إعادة كتابة المثال أو تشغيل العرض التوضيحي المباشر (انظر أيضًا شفرة المصدر ).

ولكن كيف يعمل؟


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

مرة أخرى: ينتظر يعمل فقط في وظائف غير متزامنة.

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

let response = await fetch('coffee.jpg')

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

هذا يعني أنه عندما نستدعي وظيفة myFetch () ، فإنها ترجع وعدًا ، حتى نتمكن من إضافة .then () إليها ، حيث نتعامل مع عرض الصورة على الشاشة.

ربما تعتقد أن "هذا رائع!" وأنت على حق - عدد أقل من الكتل. () للالتفاف على الكود ، يبدو كل هذا ككود متزامن ، لذلك فهو بديهي.

أضف معالجة الأخطاء


إذا كنت تريد إضافة معالجة الأخطاء ، فلديك العديد من الخيارات.

يمكنك استخدام بنية try ... catch المتزامنة مع عدم التزامن / الانتظار. هذا المثال هو نسخة موسعة من الكود أعلاه:

async function myFetch(){
    try{
        let response = await fetch('coffee.jpg')
        let myBlob = await response.blob()

        let objectURL = URL.createObjectURL(myBlob)
        let image = document.createElement('img')
        image.src = objectURL
        document.body.appendChild(image)
    } catch(e){
        console.log(e)
    }
}

myFetch()

يقبل كتلة catch () {} كائن خطأ ، والذي أطلقنا عليه اسم "e" ؛ الآن يمكننا إخراجها إلى وحدة التحكم ، وهذا سيسمح لنا بتلقي رسالة حول مكان حدوث الخطأ في التعليمات البرمجية.

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

async function myFetch(){
    let response = await fecth('coffee.jpg')
    return await response.blob()
}

myFetch().then((blob) => {
    let objectURL = URL.createObjectURL
    let image = document.createElement('img')
    image.src = objectURL
    document.body.appendChild(image)
}).catch(e => console.log(e))

هذا ممكن لأن كتلة .catch () ستلتقط الأخطاء التي تحدث في كل من الوظيفة غير المتزامنة وفي سلسلة الوعد. إذا كنت تستخدم كتلة المحاولة / الالتقاط هنا ، فلن تتمكن من معالجة الأخطاء التي تحدث عند استدعاء وظيفة myFetch ().

يمكنك العثور على كلا المثالين على GitHub:
simple-fetch-async-await-try-catch.html (انظر المصدر )

simple-fetch-async-await-prom-catch.html (انظر المصدر )

في انتظار Promise.all ()


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

مع المتزامن / انتظار (انظر عرض حي و شفرة المصدر ) يبدو مثل هذا:

async function fetchAndDecode(url, type){
    let repsonse = await fetch(url)

    let content

    if(type === 'blob'){
        content = await response.blob()
    } else if(type === 'text'){
        content = await response.text()
    }

    return content
}

async function displayContent(){
    let coffee = fetchAndDecode('coffee.jpg', 'blob')
    let tea = fetchAndDecode('tea.jpg', 'blob')
    let description = fetchAndDecode('description.txt', 'text')

    let values = await Promise.all([coffee, tea, description])

    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)
}

displayContent()
.catch(e => console.log(e))

لقد جعلنا وظيفة fetchAndDecode () بسهولة غير متزامنة مع بعض التغييرات. انتبه إلى الخط:

let values = await Promise.all([coffee, tea, description])

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

لمعالجة الأخطاء ، أضفنا كتلة .catch () إلى دعوتنا لعرض displayContent () ؛ يعالج أخطاء كلتا الدالتين.

تذكر: يمكنك أيضًا استخدام كتلة .finally () للحصول على تقرير حول العملية - يمكنك رؤيته عمليًا في العرض التوضيحي المباشر الخاص بنا (انظر أيضًا شفرة المصدر ).

غير متزامن / انتظار عيوب


Async / ينتظر بعض العيوب.

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

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

هناك نمط تصميم للتخفيف من هذه المشكلة - تعطيل جميع عمليات الوعد من خلال تخزين كائنات Promise في متغيرات ثم انتظارها. دعونا نلقي نظرة على كيفية تنفيذ ذلك.

لدينا مثالان تحت تصرفنا: slow-async-await.html (انظر شفرة المصدر ) و fast-async-await.html (انظر شفرة المصدر ). يبدأ كلا المثالين بوظيفة الوعد التي تحاكي عملية غير متزامنة باستخدام setTimeout ():

function timeoutPromise(interval){
    return new Promise((resolve, reject) => {
        setTimeout(function(){
            resolve('done')
        }, interval)
    })
}

ثم يتبع timeTest () الدالة غير المتزامنة ، التي تتوقع ثلاث مكالمات إلى timeoutPromise ():

async function timeTest(){
    ...
}

تنتهي كل من المكالمات الثلاثة إلى timeTest () بسجل للوقت المستغرق للوفاء بالوعد ، ثم يتم تسجيل الوقت الذي يستغرقه إكمال العملية بالكامل:

let startTime = Date.now()
timeTest().then(() => {
    let finishTime = Date.now()
    let timeTaken = finishTime - startTime
    alert('Time taken in milliseconds: ' + timeTaken)
})

في كل حالة ، تختلف الدالة timeTest ().

في time-async-await.html يبدو timeTest () كما يلي:

async function timeTest(){
    await timeoutPromise(3000)
    await timeoutPromise(3000)
    await timeoutPromise(3000)
}

هنا نتوقع فقط ثلاث مكالمات إلى timeoutPromise ، كل مرة تحدد تأخير 3 ثوانٍ. كل مكالمة تنتظر إتمام المكالمة السابقة - إذا قمت بتشغيل المثال الأول ، سترى نافذة مشروطة في حوالي 9 ثوانٍ.

في fast-async-await.html timeTest () يبدو كالتالي:

async function timeTest(){
    const timeoutPromise1 = timeoutPromise(3000)
    const timeoutPromise2 = timeoutPromise(3000)
    const timeoutPromise3 = timeoutPromise(3000)

    await timeoutPromise1
    await timeoutPromise2
    await timeoutPromise3
}

نحفظ هنا ثلاثة كائنات Promise في المتغيرات ، مما يؤدي إلى تشغيل العمليات المرتبطة بها في وقت واحد.

علاوة على ذلك ، نتوقع نتائجها - مع بدء تنفيذ الوعود في وقت واحد ، سيتم أيضًا إكمال الوعود في نفس الوقت ؛ عند تشغيل المثال الثاني ، سترى نافذة مشروطة في حوالي 3 ثوان!

يجب عليك اختبار الرمز بعناية ووضع ذلك في الاعتبار مع تقليل الأداء.

هناك إزعاج ثانوي آخر هو الحاجة إلى تغليف الوعود المتوقعة في وظيفة غير متزامنة.

استخدام غير متزامن / انتظار مع الفئات


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

class Person{
    constructor(first, last, age, gender, interests){
        this.name = {
            first,
            last
        }
        this.age = age
        this.gender = gender
        this.interests = interests
    }

    async greeting(){
        return await Promise.resolve(`Hi! I'm ${this.name.first}`)
    }

    farewell(){
        console.log(`${this.name.first} has left the building. Bye for now!`)
    }
}

let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling'])

يمكن استخدام طريقة الفصل على النحو التالي:

han.greeting().then(console.log)

دعم المتصفح


إحدى العوائق التي تحول دون استخدام المزامنة / انتظار عدم وجود دعم للمتصفحات القديمة. هذه الميزة متاحة في جميع المتصفحات الحديثة تقريبًا ، وكذلك الوعود. توجد بعض المشاكل في Internet Explorer و Opera Mini.

إذا كنت تريد استخدام async / انتظار ، ولكنك بحاجة إلى دعم المتصفحات القديمة ، يمكنك استخدام مكتبة BabelJS - فهي تسمح لك باستخدام أحدث JS ، وتحويلها إلى متصفح خاص بالمتصفح.

استنتاج


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

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

الترميز سعيدة!

All Articles