الواجهة البرمجية Promise في JavaScript


سارة محمد2

الواجهة البرمجية Promise رائعة ويمكنك جعلها مذهلة باستخدام async و await.

إنّ الشيفرة المتزامنة سهلة التتبع والتنقيح إلا أنّ الشيفرة غير المتزامنة أفضل بشكل عام من حيث الأداء والمرونة، فلماذا "توقف العرض" بينما بإمكانك استقبال الكثير من الطلبات في وقت واحد ثمّ معالجة كلٍّ منها عندما يصبح جاهزًا؟ مع العديد من الواجهات البرمجية API الجديدة التي تم تحقيقها مع مبدأ الوعد، إذ أصبحت الوعود جزءًا كبيرًا في عالم الجافاسكربت. لنلقِ نظرة على كيفية استخدام الواجهة البرمجية للوعود.

الوعود قيد التطبيق

الواجهة البرمجية XMLHttpRequest غير متزامنة ولكنها لا تستخدم الواجهة البرمجية Promises. هناك عدة واجهات برمجية أصلية تستخدم الوعود الآن، مثل:

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

من المحتمل أن يكون اختبار الوعود أسهل مما تعتقد لأنه يمكنك استخدام setTimeout كـ "مهمة" لك غير متزامنة.

استخدام Promise الأساسي

يجب أن يستخدم الباني new Promise()‎ فقط للمهام الغير متزامنة الموروثة، مثل استخدام setTimeout أو XMLHttpRequest. يتم إنشاء وعدًا جديدًا باستخدام الكلمة المفتاحية new ويتم تمرير توابع resolve و reject لردّ النداء المزوَّد:

var p = new Promise(function(resolve, reject) {

    // ...القيام بمهمة غير متزامنة وثم

    if(/* شرط جيد */) {
        resolve('Success!');
    }
    else {
        reject('Failure!');
    }
});

p.then(function(result) { 
    /* القيام بفعل ما مع النتيجة */
}).catch(function() {
    /* خطأ :( */
}).finally(function() {
   /* تنفّذ بغض النظر عن النجاح أو الفشل */ 
});

يعود الأمر للمطور فيما إذا كان يريد استدعاء resolve أو reject يدويًا ضمن جسم رد النداء اعتمادًا على نتيجة المهمة المعطاة. مثال واقعي لتحويل XMLHttpRequest إلى مهمة تعتمد على الوعد:

// من وعود جاك أرشيبالد والعودة
// http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promisifying-xmlhttprequest

function get(url) {
  //إعادة وعد جديد.
  return new Promise(function(resolve, reject) {
    // XHR القيام بالعمل الاعتيادي لـ
    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      // هذا يتم استدعاؤه حتى في حالة 404
      // لذا اختبر الحالة
      if (req.status == 200) {
        // قم بإنهاء الوعد مع نص الرد
        resolve(req.response);
      }
      else {
        // وإلا ارفض مع نص الحالة
        // والذي نأمل أن يكون خطأ ذو معنى
        reject(Error(req.statusText));
      }
    };

    // معالجة أخطاء الشبكة
    req.onerror = function() {
      reject(Error("Network Error"));
    };

    // القيام بالطلب
    req.send();
  });
}

// استخدمه!
get('story.json').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.error("Failed!", error);
});

إذا كان من الممكن اتخاذ إجراء غير متزامن قد لا تحتاج إلى إكمال المهام غير المتزامنة ضمن الوعد، لكن سيكون الأفضل هو أن تكون القيمة المعادة وعدًا لذا يمكنك أن تعدّ كم عدد الوعود التي حصلت عليها من تابع معطى. في تلك الحالة يمكنك ببساطة استدعاء Promise.resolve()‎ أو Promise.reject()‎ بدون استخدام الكلمة المفتاحية new. مثلًا:

var userCache = {};

function getUserDetail(username) {
  // في كلتا الحالتين، تم إضافته إلى الذاكرة أو لا، سيتم إعادة وعد

  if (userCache[username]) {
      // new إعادة وعد بدون الكلمة المفتاحية 
    return Promise.resolve(userCache[username]);
  }

  //  لتحصل على المعلومات fetch استخدم الواجهة البرمجية
  // وعدًا fetch تعيد 
  return fetch('users/' + username + '.json')
    .then(function(result) {
      userCache[username] = result;
      return result;
    })
    .catch(function() {
      throw new Error('Could not find user: ' + username);
    });
}

بما أن القيمة المعادة هي وعد لذا يمكنك استخدام التوابع then و catch عليها.

then

كل كائنات الوعد تملك التابع then الذي يسمح لك بالتفاعل مع الوعد. رد النداء الأول لتابع then يستدعي النتيجة المعطاة له عن طريق استدعاء ()resolve:

new Promise(function(resolve, reject) {
    // setTimeout حدث زائف غير متزامن باستخدام 
    setTimeout(function() { resolve(10); }, 3000);
})
.then(function(result) {
    console.log(result);
});

// console من الطرفية 
// 10

رد النداء لـ then يتم تشغيله عندما ينتهي الوعد. يمكنك أيضًا أن تسلسل ردود النداء للتابع then:

new Promise(function(resolve, reject) { 
    // setTimeout حدث زائف غير متزامن باستخدام 
    setTimeout(function() { resolve(10); }, 3000);
})
.then(function(num) { console.log('first then: ', num); return num * 2; })
.then(function(num) { console.log('second then: ', num); return num * 2; })
.then(function(num) { console.log('last then: ', num);});

// من الـ console
// first then:  10
// second then:  20
// last then:  40

كل then تستقبل القيمة المعادة من استدعاء then السابق.

إذا أُنهي الوعد قبل أن يتم استدعاء التابع then مجددًا، يتم إيقاف رد النداء مباشرةً. إذا تم رفض الوعد ثم استدعاء التابع then بعد الرفض، لا يتم استدعاء رد النداء أبدًا.

catch

يتم استدعاء رد النداء catch عندما يُرفض الوعد:

new Promise(function(resolve, reject) {
    // setTimeout حدث زائف غير متزامن باستخدام 
    setTimeout(function() { reject('Done!'); }, 3000);
})
.then(function(e) { console.log('done', e); })
.catch(function(e) { console.log('catch: ', e); });

// console من الطرفية 
// 'catch: Done!'

ما تمرره لتابع reject يعود لك ولكن النمط المعتاد هو إرسال Error لـ catch:

reject(Error('Data could not be found'));

finally

رد النداء المعرّف حديثًا finally يتم استدعاؤه بغض النظر عن النجاح أو الفشل:

(new Promise((resolve, reject) => { reject("Nope"); }))
    .then(() => { console.log("success") })
    .catch(() => { console.log("fail") })
    .finally(res => { console.log("finally") });

// >> fail
// >> finally

Promise.all

فكر بمحمّلات الجافاسكربت: يوجد أوقات يتم فيها تشغيل عدة تفاعلات غير متزامنة وتريد الاستجابة عندما تكتمل جميعها، هنا يأتي دور التابع Promise.all، هذا التابع يأخذ مصفوفة من الوعود ويعطيك رد نداء واحد فقط عند إنهاء جميع الوعود.

Promise.all([promise1, promise2]).then(function(results) {
    // كلا الوعدين تم إنهاؤهما
})
.catch(function(error) {
    // تم رفض وعد واحد أو أكثر
});

الطريقة المثالية للتفكير بـ Promise.all هي إطلاق عدة طلبات AJAX (باستخدام fetch) في نفس الوقت.

var request1 = fetch('/users.json');
var request2 = fetch('/articles.json');

Promise.all([request1, request2]).then(function(results) {
    // كلا الوعدين تم تنفيذهما
});

يمكنك أن تدمج عدة واجهات برمجية مثل fetch والواجهة البرمجية Battery بما أنّها تعيد وعودًا.

Promise.all([fetch('/users.json'), navigator.getBattery()]).then(function(results) {
    // كلا الوعدين تم تنفيذهما
});

التعامل مع الرفض صعب بالطبع. إذا تم رفض أيّ وعد سيتم إطلاق catch للرفض الأول:

var req1 = new Promise(function(resolve, reject) { 
    // setTimeout حدث زائف غير متزامن باستخدام  
    setTimeout(function() { resolve('First!'); }, 4000);
});
var req2 = new Promise(function(resolve, reject) { 
    // setTimeout حدث زائف غير متزامن باستخدام  
    setTimeout(function() { reject('Second!'); }, 3000);
});
Promise.all([req1, req2]).then(function(results) {
    console.log('Then: ', results);
}).catch(function(err) {
    console.log('Catch: ', err);
});

// console من الـ 
// Catch: Second!

ستكون Promise.all مفيدة أكثر للواجهات البرمجية التي تتجه لاستخدام الوعود.

Promise.race

تعدّ الدالة Promise.race مفيدة فبدلًا من أن يتم الانتظار حتى إنهاء جميع الوعود أو رفضها تقوم بتشغيل أيّ وعد في المصفوفة تم إنهاؤه أو رفضه.

var req1 = new Promise(function(resolve, reject) { 
    // setTimeout حدث زائف غير متزامن باستخدام 
    setTimeout(function() { resolve('First!'); }, 8000);
});
var req2 = new Promise(function(resolve, reject) { 
    // setTimeout حدث زائف غير متزامن باستخدام 
    setTimeout(function() { resolve('Second!'); }, 3000);
});
Promise.race([req1, req2]).then(function(one) {
    console.log('Then: ', one);
}).catch(function(one, two) {
    console.log('Catch: ', one);
});

// console من الـ 
// Then: Second!

يمكن أن تشغّل حالة الاستخدام طلبًا إلى مصدر أوليّ ومصدر ثانوي (في حال عدم توفر الأوليّ والثانوي).

اعتد على الوعود

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

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

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

ترجمة -وبتصرف- للمقال JavaScript Promise API لصاحبه David Walsh





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن