٣١ يوليو ٢٠٢٠

Async/await (غير المتزامن /الانتظار)

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

الدوال غير المتزامنة

لنبدأ بالكلمة الرئيسية async. يمكن وضعها قبل دالة ، مثل هذا:

async function f() {
  return 1;
}

تعني كلمة “غير متزامن” قبل دالة شيء واحد بسيط: ترجع الدالة دائمًا الوعد. يتم تغليف القيم الأخرى في وعد تم حله تلقائيًا.

على سبيل المثال ، ترجع هذه الوظيفة وعدًا تم حله بنتيجة 1 ؛ دعنا نختبره:

async function f() {
  return 1;
}

f().then(alert); // 1

… يمكن أن نعيد وعدًا صريحًا ، والذي سيكون نفسه:

async function f() {
  return Promise.resolve(1);
}

f().then(alert); // 1

لذا ، يضمن غير متزامن '' أن ترجع الدالة promise ، وتلف غير وعود فيه. بسيطة بما يكفي ، أليس كذلك؟ ولكن ليس هذا فقط. هناك كلمة رئيسية أخرى تنتظر ‘’ تعمل فقط داخل وظائف `غير متزامن '، وهي رائعة جدًا.

Await

الصيغة:

// works only inside async functions
let value = await promise;

تجعل الكلمة الرئيسية “في انتظار” جافا سكريبت تنتظر حتى يستقر هذا الوعد ويعيد نتيجته.

في ما يلي مثال بوعد يتم حله في ثانية واحدة:

async function f() {

  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("done!"), 1000)
  });

  let result = await promise; // wait until the promise resolves (*)

  alert(result); // "done!"
}

f();

تنفيذ الوظيفة “يتوقف” عند السطر (*) ويستأنف عندما يستقر الوعد ، ويصبح “النتيجة” نتيجته. لذلك يظهر الرمز أعلاه “تم!” في ثانية واحدة.

دعونا نؤكد: `` انتظارًا ‘’ يجعل جافا سكريبت تنتظر حتى يستقر الوعد ، ثم استمر في النتيجة. هذا لا يكلف أي موارد وحدة المعالجة المركزية ، لأن المحرك يمكنه القيام بمهام أخرى في الوقت نفسه: تنفيذ البرامج النصية الأخرى ، والتعامل مع الأحداث ، وما إلى ذلك.

إنها مجرد بنية أكثر أناقة للحصول على نتيجة الوعد من “الوعد. ثم” ، أسهل للقراءة والكتابة.

لا يمكن استخدام` انتظار 'في الوظائف العادية

إذا حاولنا استخدام `` انتظار ‘’ في وظيفة غير متزامنة ، فسيكون هناك خطأ في بناء الجملة:

function f() {
  let promise = Promise.resolve(1);
  let result = await promise; // Syntax error
}

سنحصل على هذا الخطأ إذا لم نضع async قبل دالة. كما ذكر ، يعمل `` انتظار ‘’ فقط داخل وظيفة غير متزامنة.

لنأخذ مثال showAvatar () من الفصل <info: prom-chaining> ونعيد كتابته باستخدام async / await:

  1. سنحتاج إلى استبدال مكالمات “.then” بـ “ينتظر”.
  2. كما يجب أن نجعل الوظيفة “غير متزامنة” حتى تعمل.
async function showAvatar() {

  // read our JSON
  let response = await fetch('/article/promise-chaining/user.json');
  let user = await response.json();

  // read github user
  let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
  let githubUser = await githubResponse.json();

  // show the avatar
  let img = document.createElement('img');
  img.src = githubUser.avatar_url;
  img.className = "promise-avatar-example";
  document.body.append(img);

  // wait 3 seconds
  await new Promise((resolve, reject) => setTimeout(resolve, 3000));

  img.remove();

  return githubUser;
}

showAvatar();

نظيفة جدا وسهلة القراءة ، أليس كذلك؟ أفضل بكثير من ذي قبل.

انتظار ‘’ لن يعمل في رمز المستوى الأعلى

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

// syntax error in top-level code
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();

But we can wrap it into an anonymous async function, like this:

(async () => {
  let response = await fetch('/article/promise-chaining/user.json');
  let user = await response.json();
  ...
})();
في انتظار` يقبل " ثماني \

مثل "prom.then" ، يتيح لنا "await" استخدام العناصر القابلة للاستعمال (تلك التي تستخدم طريقةثمالقابلة للاستدعاء). الفكرة هي أن كائن طرف ثالث قد لا يكون وعدًا ، ولكنه متوافق مع الوعد: إذا كان يدعم.then، فهذا يكفي لاستخدامه مع `بانتظار ‘’.

إليك فئة `` Thenable` التجريبية ؛ تقبل “الانتظار” أدناه حالاتها:

class Thenable {
  constructor(num) {
    this.num = num;
  }
  then(resolve, reject) {
    alert(resolve);
    // resolve with this.num*2 after 1000ms
    setTimeout(() => resolve(this.num * 2), 1000); // (*)
  }
};

async function f() {
  // waits for 1 second, then result becomes 2
  let result = await new Thenable(1);
  alert(result);
}

f();

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

أساليب فئة Async

للإعلان عن أسلوب فئة غير متزامن ، ما عليك سوى إلحاقها بـ `غير متزامن ':

class Waiter {
  async wait() {
    return await Promise.resolve(1);
  }
}

new Waiter()
  .wait()
  .then(alert); // 1

The meaning is the same: it ensures that the returned value is a promise and enables await.

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

إذا تم حل الـ promise بشكل طبيعي ، فإن “انتظار الوعد” يُرجع النتيجة. ولكن في حالة الرفض ، فإنه يلقي الخطأ ، كما لو كان هناك بيان `رمي 'في ذلك السطر.

هذا الكود:

async function f() {
  await Promise.reject(new Error("Whoops!"));
}

…is the same as this:

async function f() {
  throw new Error("Whoops!");
}

في المواقف الحقيقية ، قد يستغرق الوعد بعض الوقت قبل أن يرفض. في هذه الحالة ، سيكون هناك تأخير قبل أن يطرح “انتظار” خطأ.

يمكننا اكتشاف هذا الخطأ باستخدام try..catch ، بالطريقة نفسها التي تستخدمهارمية عادية:

async function f() {

  try {
    let response = await fetch('http://no-such-url');
  } catch(err) {
    alert(err); // TypeError: failed to fetch
  }
}

f();

في حالة حدوث خطأ ، ينتقل عنصر التحكم إلى كتلة “الالتقاط”. يمكننا أيضًا لف خطوط متعددة:

async function f() {

  try {
    let response = await fetch('/no-user-here');
    let user = await response.json();
  } catch(err) {
    // catches errors both in fetch and response.json
    alert(err);
  }
}

f();

إذا لم يكن لدينا “try…catch” ، فسيتم رفض الوعد الذي تم إنشاؤه بواسطة استدعاء دالة async f (). يمكننا إلحاق “.catch” للتعامل معها:

async function f() {
  let response = await fetch('http://no-such-url');
}

// f() becomes a rejected promise
f().catch(alert); // TypeError: failed to fetch // (*)

إذا نسينا إضافة “.catch” هناك ، فإننا نتلقى خطأ وعد غير معالج (قابل للعرض في وحدة التحكم). يمكننا اكتشاف مثل هذه الأخطاء باستخدام معالج الأحداث unhandledrejection العالمي كما هو موضح في الفصل <info: prom-error-handling>.

async / await وprom.then / catch

عندما نستخدم “غير متزامن / انتظار” ، نادرًا ما نحتاج إلى “. ثم” ، لأن “انتظار” يتعامل مع الانتظار. ويمكننا استخدام ‘try…catchالعادية بدلاً من.atch’. هذا عادة (ولكن ليس دائما) أكثر ملاءمة.

ولكن في المستوى العلوي من الشفرة ، عندما نكون خارج أي وظيفة “غير متزامن” ، يتعذر علينا استخدام “انتظار” من الناحية النحوية ، لذلك من المعتاد إضافة “.then / catch” للتعامل مع النتيجة النهائية أو خطأ السقوط ، كما هو الحال في السطر (*) من المثال أعلاه.

async / await يعمل بشكل جيد مع

عندما نحتاج إلى انتظار وعود متعددة ، يمكننا أن نلفها في “Promise.all” ثم “انتظر”:

// wait for the array of results
let results = await Promise.all([
  fetch(url1),
  fetch(url2),
  ...
]);

في حالة وجود خطأ ، يتم نشره كالمعتاد ، من الوعد الفاشل إلى “Promise.all” ، ثم يصبح استثناء يمكننا التقاطه باستخدام “try…catch” حول المكالمة.

## ملخص

الكلمة الأساسية `غير متزامن 'قبل دالة لها تأثيران:

1. يجعلها دائما تعيد الوعد.
2. يسمح باستخدام "ينتظر" لاستخدامه.

تجعل الكلمة الرئيسية "في انتظار" قبل الوعد جافا سكريبت تنتظر حتى يستقر هذا الوعد ، ثم:

1. إذا كان هناك خطأ ، فسيتم إنشاء الاستثناء - مثل استدعاء "خطأ في الخطأ" في ذلك المكان.
2. وإلا ، فإنها ترجع النتيجة.

يوفرون معًا إطارًا رائعًا لكتابة رمز غير متزامن يسهل قراءته وكتابته.

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

مهمه

أعد كتابة رمز المثال هذا من الفصل <info: prom-chaining> باستخدام async / await بدلاً من.then / catch:

function loadJson(url) {
  return fetch(url)
    .then(response => {
      if (response.status == 200) {
        return response.json();
      } else {
        throw new Error(response.status);
      }
    })
}

loadJson('no-such-user.json')
  .catch(alert); // Error: 404

الملاحظات أسفل الكود:

async function loadJson(url) { // (1)
  let response = await fetch(url); // (2)

  if (response.status == 200) {
    let json = await response.json(); // (3)
    return json;
  }

  throw new Error(response.status);
}

loadJson('no-such-user.json')
  .catch(alert); // Error: 404 (4)

الملاحظات:

  1. تصبح الدالة `loadJson`` غير متزامنة ". 2- يتم استبدال جميع “.then” بالداخل بـ “ينتظر”.

  2. يمكننا `` response response.json () `بدلاً من انتظارها ، كما يلي:

    شبيبة if (response.status == 200) { رد العودة. json () ؛ // (3) }} ``

    ثم يجب أن ينتظر الكود الخارجي `` ينتظر ‘’ حتى يتم حل هذا الوعد. في حالتنا لا يهم.

  3. تتم معالجة الخطأ الذي تم طرحه من “loadJson” بواسطة “.catch”. لا يمكننا استخدام `` انتظار انتظار Json (…) ، لأننا لسنا في وظيفة غير متزامن '.

يمكنك العثور أدناه على مثال “إعادة النمو” من الفصل <info: prom-chaining>. أعد كتابته باستخدام “غير متزامن / انتظار” بدلاً من “.then / catch”.

وتخلص من العودية لصالح حلقة في demoGithubUser: مع` غير متزامن / انتظار 'يصبح من السهل القيام به.

class HttpError extends Error {
  constructor(response) {
    super(`${response.status} for ${response.url}`);
    this.name = 'HttpError';
    this.response = response;
  }
}

function loadJson(url) {
  return fetch(url)
    .then(response => {
      if (response.status == 200) {
        return response.json();
      } else {
        throw new HttpError(response);
      }
    })
}

// Ask for a user name until github returns a valid user
function demoGithubUser() {
  let name = prompt("Enter a name?", "iliakan");

  return loadJson(`https://api.github.com/users/${name}`)
    .then(user => {
      alert(`Full name: ${user.name}.`);
      return user;
    })
    .catch(err => {
      if (err instanceof HttpError && err.response.status == 404) {
        alert("No such user, please reenter.");
        return demoGithubUser();
      } else {
        throw err;
      }
    });
}

demoGithubUser();

لا توجد حيل هنا. ما عليك سوى استبدال .catch بـtry ... catch داخل demoGithubUser وإضافةasync / await عند الحاجة:

class HttpError extends Error {
  constructor(response) {
    super(`${response.status} for ${response.url}`);
    this.name = 'HttpError';
    this.response = response;
  }
}

async function loadJson(url) {
  let response = await fetch(url);
  if (response.status == 200) {
    return response.json();
  } else {
    throw new HttpError(response);
  }
}

// Ask for a user name until github returns a valid user
async function demoGithubUser() {

  let user;
  while(true) {
    let name = prompt("Enter a name?", "iliakan");

    try {
      user = await loadJson(`https://api.github.com/users/${name}`);
      break; // no error, exit loop
    } catch(err) {
      if (err instanceof HttpError && err.response.status == 404) {
        // loop continues after the alert
        alert("No such user, please reenter.");
      } else {
        // unknown error, rethrow
        throw err;
      }
    }
  }


  alert(`Full name: ${user.name}.`);
  return user;
}

demoGithubUser();

لدينا وظيفة “عادية”. كيفية استدعاء “غير متزامن” منه واستخدام نتائجه؟

async function wait() {
  await new Promise(resolve => setTimeout(resolve, 1000));

  return 10;
}

function f() {
  // ...what to write here?
  // we need to call async wait() and wait to get 10
  // remember, we can't use "await"
}

ملاحظة. المهمة بسيطة من الناحية الفنية ، ولكن السؤال شائع جدًا للمطورين الجدد على عدم المزامنة / الانتظار.

هذا هو الحال عند معرفة كيف يعمل في الداخل مفيد.

ما عليك سوى التعامل مع استدعاء غير متزامن '' على أنه وعد وإرفاق ثم ‘’ به:

async function wait() {
  await new Promise(resolve => setTimeout(resolve, 1000));

  return 10;
}

function f() {
  // shows 10 after 1 second
  wait().then(result => alert(result));
}

f();
خريطة الدورة التعليمية

التعليقات

إقرأ هذا قبل أن تضع تعليقًا…
  • إذا كان لديك اقتراحات أو تريد تحسينًا - من فضلك من فضلك إفتح موضوعًا فى جيتهاب أو شارك بنفسك بدلًا من التعليقات.
  • إذا لم تستطع أن تفهم شيئّا فى المقال - وضّح ماهو.
  • إذا كنت تريد عرض كود استخدم عنصر <code> ، وللكثير من السطور استخدم <pre>، ولأكثر من 10 سطور استخدم (plnkr, JSBin, codepen…)