الدالة Promise.prototype.then()‎ في JavaScript

من موسوعة حسوب

الدالة then()‎ تعيد وعدًا Promise، وتأخذ وسيطين على الأكثر، وهما دالتا رد النداء (callback) لنجاح أو فشل الوعد.

var promise1 = new Promise(function(resolve, reject) {
  resolve('Success!');
});

promise1.then(function(value) {
  console.log(value);
  // "Success!"
});

ملاحظة: إذا كان أحد أو كلا المعاملين غير موجود، أو كان موجودًا لكنه ليس دالةً، فلن تضع الدالة then()‎ معالجًا له، لكنها لن تولِّد أي أخطاء. إذا أصبحت هنالك حالة للوعد Promise الذي استدعيت الدالة ()then عليه، ولم يكن للدالة ()then وسائط مناسبة، فسيُنشَأ وعدٌ Promise جديد دون دوال للمعالجة، وذلك بأخذ آخر حالة للوعد Promise الأصلي الذي اُستدعيت عليه الدالة ()then.

البنية العامة

p.then(onFulfilled[, onRejected]);

p.then(function(value) {
  // القبول
}, function(reason) {
  // الرفض
});

المعاملات

onFulfilled

دالة ستُستدعى عند قبول الوعد Promise. تملك هذه الدالة معاملًا وحيدًا هو قيمة القبول. إذا لم تكن هذه الدالة موجودًا، فستستبدل داخليًا بدالة تُعيد الوسيط المُمرَّر إليها.

onRejected

هذا المعامل اختياري. وهو دالة ستُستدعى عند رفض الوعد Promise. تملك هذه الدالة معاملًا وحيدًا هو سبب الرفض. إذا لم تكن هذه الدالة موجودًا، فستستبدل داخليًا بدالة تعيد رمي الوسيط المُمرَّر إليها.

القيمة المعادة

كائن Promise في حالة الانتظار (pending). ثم ستستدعى دالة المعالجة (onFilfilled أو onRejected) استدعاءً غير متزامن، وبعد استدعاء الدالة المعالجة، إذا كانت:

  • تعيد قيمةً، فالوعد المعاد من then سيقبل مع تلك القيمة.
  • ترمي خطأً، فالوعد المعاد من then سيُرفض وسيرمى خطأٌ مع تلك القيمة.
  • تعيد وعدًا مقبولًا من قبل، وسيقبل الوعد المُعاد من then مع قيمة الوعد الأصلي.
  • تعيد وعدًا مرفوضًا من قبل، وسيرفض الوعد المعاد من then مع قيمة الوعد الأصلي.
  • تعيد كائن Promise آخر في حالة الانتظار، وسيكون قبول أو رفض الوعد المُعاد من then تابعًا لقبول أو رفض الوعد المعاد من دالة المعالجة. وستكون قيمة الوعد المعاد من الدالة then هي نفس قيمة الوعد المعُاد من دالة المعالجة.

هذا مثال يوضِّح الآلية غير المتزامنة للدالة ()then. لاحظ أننا نستخدم وعدًا مقبولًا، لذا ستستدعى ()then مباشرةً، لكن الدوال المعالجة فيها ستُنفَّذ بشكل غير متزامن كما هو واضح من الرسائل الظاهرة:

var resolvedProm = Promise.resolve(33);

var thenProm = resolvedProm.then(function(value){
    console.log("this gets called after the end of the main stack. the value received and returned is: " + value);
    return value;
});

console.log(thenProm);

// سنؤجل تنفيذ الدالة
setTimeout(function(){
    console.log(thenProm);
});


// الناتج بالترتيب هو
// Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
// "this gets called after the end of the main stack. the value received and returned is: 33"
// Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 33}

الوصف

لمّا كانت الدالة ()then والدالة Promise.prototype.catch()‎ تعيدان وعودًا (الكائن Promise)، فيمكن تنفيذها على شكل سلسلة (chain).

أمثلة

استخدام الدالة ()then

var p1 = new Promise( (resolve, reject) => {
  resolve('Success!');
  // or
  // reject ("Error!");
} );

p1.then( value => {
  console.log(value); // Success!
}, reason => {
  console.log(reason); // Error!
} );

استخدام الدالة كسلسلة

تُعيد الدالة ()then كائنًا من النوع Promise، مما يسمح باستدعاء الدوال كسلسلة (chain). إذا كانت الدالة المُمرَّرة كدالة معالجة إلى الدالة ()then تُعيد كائن Promise، فيمكن أن يكون الكائن Promise المعاد يستطيع أن ينفِّذ الدالة ()then.

المثال الآتي يحاكي شيفرة غير متزامنة مع الدالة setTimeout:

  • الخطوة الأولى هي الحصول على foo ودمج bar إليه، وقبول الوعد واستدعاء ()then.
  • الخطوة الثانية هي الحصول على foobar، وتسجيل دالة رد نداء (callback) التي تجري عملية على تلك السلسلة النصية، وتطبعها، لكن ذلك لن يحدث قبل إعادة السلسلة النصية الأصلية إلى الدالة ()then التالية.
  • الخطوة الثالثة هي طباعة رسائل مفيدة.

لاحظ أنَّ السلسلة النصية الأخيرة string لن تحتوي على baz في آخرها، وذلك لأننا أضفنا ذلك بشكل غير متزامن داخل الدالة setTimeout:

Promise.resolve('foo')
  // الخطوة الأولى
  .then(function(string) {
    return new Promise(function(resolve, reject) {
      setTimeout(function() {
        string += 'bar';
        resolve(string);
      }, 1);
    });
  })
  // الخطوة الثانية
  .then(function(string) {
    setTimeout(function() {
      string += 'baz';
      console.log(string);
    }, 1)
    return string;
  })
  // الخطوة الثالثة
  .then(function(string) {
    console.log("Last Then:  oops... didn't bother to instantiate and return " +
                "a promise in the prior then so the sequence may be a bit " +
                "surprising");

    console.log(string);
  });

عند إعادة القيمة ببساطة داخل دالة معالجة في ()then، فستُعاد Promise.resolve (<القيمة المعادة>):

var p2 = new Promise(function(resolve, reject) {
  resolve(1);
});

p2.then(function(value) {
  console.log(value); // 1
  return value + 1;
}).then(function(value) {
  console.log(value + '- This synchronous usage is virtually pointless'); // 2- This synchronous usage is virtually pointless
});

p2.then(function(value) {
  console.log(value); // 1
});

استدعاء ()then سيعيد وعدًا مرفوضًا إذا رمت الدالة استثناءً، أو أعادت وعدًا مرفوضًا:

Promise.resolve()
  .then( () => {
    // رمي استثناء
    throw 'Oh no!';
  })
  .then( () => { 
    console.log( 'Not called.' );
  }, reason => {
    console.error( 'onRejected function called: ' + reason );
  });

في جميع الحالات الأخرى، يعاد وعدًا مقبولًا. في المثال التالي، سيعيد الاستدعاء ()then الأول القيمة 42 مغلفة بوعد مقبول رغم رفض الوعد السابق في السلسلة نفسها:

Promise.reject()
  .then( () => 99, () => 42 ) // القيمة 42 مغلفةً بوعد مقبول onRejected يعيد
  .then( solution => console.log( 'Resolved with ' + solution ) ); // 42 يقبل الوعد مع القيمة

عمليًّا، يفضل التقاط الوعود المرفوضة بدلًا من استعمال حالة الصياغة الثنائية كما موضح في المثال التالي:

Promise.resolve()
  .then( () => {
    // يعيد وعدًا مرفوضًا .then() جعل
    throw new Error('Oh no!');
  })
  .catch( error => {
    console.error( 'onRejected function called: ' + error.message );
  })
  .then( () => {
    console.log( "I am always called even if the prior then's promise rejects" );
  });

يمكن ربط الاستدعاءات بسلسلة لتنفيذ دالة واحدة مع واجهة برمجية تعتمد على الوعود (Promise-based API) فوق دالة أخرى:

function fetch_current_data() {
  // وعدًا. تعرض هذه الدالة fetch() تعيد الواجهة البرمجية
  // واجهة برمجية مماثلة باستثناء أن القيمة المنجزة لوعد 
  // هذه الدالة لديه عملًا إضافيًّا لينجزه معها
  return fetch('current-data.json').then((response) => {
    if (response.headers.get('content-type') != 'application/json') {
      throw new TypeError();
    }
    var j = response.json();
    // j يمكن فعل شيء مع
    return j; // تعطى القيمة المنجزة لمستخدم
              // fetch_current_data().then()
  });
}
I

إن أعاد onFulfilled وعدًا، فسيقبل أو يرفض هذا الوعد قيمة ()then:

function resolveLater(resolve, reject) {
  setTimeout(function () {
    resolve(10);
  }, 1000);
}
function rejectLater(resolve, reject) {
  setTimeout(function () {
    reject(new Error('Error'));
  }, 1000);
}

var p1 = Promise.resolve('foo');
var p2 = p1.then(function() {
  // إعادة وعد هنا والذي سيقبل مع القيمة 10 بعد 1 ثانية
  return new Promise(resolveLater);
});
p2.then(function(v) {
  console.log('resolved', v);  // "resolved", 10
}, function(e) {
  // لا تستدع
  console.log('rejected', e);
});

var p3 = p1.then(function() {
  // بعد 1 ثانية 'Error' إعادة وعد هنا والذي سيرفض مع خطأ
  return new Promise(rejectLater);
});
p3.then(function(v) {
  // لا تستدع
  console.log('resolved', v);
}, function(e) {
  console.log('rejected', e); // "rejected", 'Error'
});

نقص الدعم للنمط window.setImmediate الذي يعتمد على الوعود

استعمال التابع Function.prototype.bind()‎ ‏‎ Reflect.apply ‎‏(Reflect.apply()‎) لإنشاء دالة بنمط setImmediate غير قابلة للإلغاء (non-cancellable):

const nextTick = (()=>{
  const noop = () => {}; // حرفيًّا
  const nextTickPromise = () => Promise.resolve().then(noop);

  const rfab = Reflect.apply.bind; // (thisArg, fn, thisArg, [...args])
  const nextTick = (fn, ...args) => (
    fn !== undefined
    ? Promise.resolve(args).then(rfab(null, fn, null))
    : nextTickPromise(),
    undefined
  );
  nextTick.ntp = nextTickPromise;

  return nextTick;
})();
S

انظر أيضًا

  • الدالة catch()‎: تعيد وعدًا Promise وتتعامل مع حالات رفض الوعود فقط.
  • الدالة finally()‎: تعيد وعدًا Pormise، وعندما تُقرَّر قيمة الوعد، سواءً كان مقبولًا أو مرفوضًا، فستُنفَّذ دالة رد النداء (callback) المُحدَّدة.

المصادر