javascript - كيف أعيد الاستجابة من مكالمة غير متزامنة؟




ajax asynchronous (20)

لدي وظيفة foo التي تجعل طلب Ajax. كيف يمكنني إرجاع الرد من foo ؟

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

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            result = response;
            // return response; // <- I tried that one as well
        }
    });

    return result;
}

var result = foo(); // It always ends up being `undefined`.

→ للحصول على شرح أكثر عمومية لسلوك التزامن مع أمثلة مختلفة ، يرجى الاطلاع على سبب عدم تغيير المتغير الخاص بي بعد تعديله داخل إحدى الوظائف؟ - مرجع شفرة غير متزامن

← إذا فهمت المشكلة بالفعل ، فانتقل إلى الحلول الممكنة أدناه.

المشكلة

The A in Ajax تعني asynchronous . وهذا يعني أن إرسال الطلب (أو بالأحرى استلام الاستجابة) يتم أخذه من تدفق التنفيذ العادي. في المثال الخاص بك ، يعود $.ajax فورًا $.ajax التالية ، return result; ، يتم تنفيذه قبل الدالة التي تم تمريرها كما تم استدعاء رد الاتصال حتى.

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

متزامن

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

يحدث الشيء نفسه عند إجراء مكالمة دالة تحتوي على رمز "عادي":

function findItem() {
    var item;
    while(item_not_found) {
        // search
    }
    return item;
}

var item = findItem();

// Do something with item
doSomethingElse();

على الرغم من أن findItem قد يستغرق وقتًا طويلاً للتنفيذ ، فإن أي رمز يأتي بعد var item = findItem(); يجب الانتظار حتى تقوم الدالة بإرجاع النتيجة.

غير متزامن

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

هذا هو بالضبط ما يحدث عند قيامك بعمل طلب أجاكس.

findItem(function(item) {
    // Do something with item
});
doSomethingElse();

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

محاليل)

احتضان الطبيعة غير المتزامنة لجافا سكريبت! على الرغم من أن بعض العمليات غير المتزامنة توفر نظراء متزامنين (كما يفعل "Ajax") ، إلا أنه من المستبعد عموما استخدامها ، خاصة في سياق المتصفح.

لماذا هو سيء تسأل؟

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

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

في ما يلي سننظر في ثلاثة حلول مختلفة تستند جميعها إلى بعضها البعض:

  • وعود مع async/await (ES2017 + ، متوفرة في المتصفحات القديمة إذا كنت تستخدم برنامج التحويل البرمجي أو regenerator)
  • عمليات رد الاتصال (الشائعة في العقدة)
  • تتعهد بذلك ( then() (ES2015 +) ، والمتوفر في المتصفحات القديمة إذا كنت تستخدم واحدة من المكتبات الوعدية العديدة)

هذه الثلاثة متوفرة في المتصفحات الحالية ، والعقدة 7+.

ES2017 +: وعود مع async/await

قدم إصدار ECMAScript الصادر في عام 2017 دعمًا على مستوى التركيب للوظائف غير المتزامنة. بمساعدة async ، يمكنك كتابة غير متزامن في "نمط متزامن". لا يزال الرمز غير متزامن ، ولكنه أسهل في القراءة / الفهم.

async/await يبني على أعلى الوعود: وظيفة async دائمًا تعيد الوعد. await "الفوائض" وعدًا وينتج عن ذلك قيمة تم حل الوعد بها أو يرتكب خطأ إذا تم رفض الوعد.

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

يمكنك قراءة المزيد عن async/await وانتظر على MDN.

في ما يلي مثال يعتمد على التأخير أعلاه:

// Using 'superagent' which will return a promise.
var superagent = require('superagent')

// This is isn't declared as `async` because it already returns a promise
function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}


async function getAllBooks() {
  try {
    // GET a list of book IDs of the current user
    var bookIDs = await superagent.get('/user/books');
    // wait for 3 seconds (just for the sake of this example)
    await delay();
    // GET information about each book
    return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
  } catch(error) {
    // If any of the awaited promises was rejected, this catch block
    // would catch the rejection reason
    return null;
  }
}

// Async functions always return a promise
getAllBooks()
  .then(function(books) {
    console.log(books);
  });

browser الحالي node الإصدارات async/await . يمكنك أيضًا دعم البيئات القديمة عن طريق تحويل رمزك إلى ES5 بمساعدة regenerator (أو الأدوات التي تستخدم المجدد ، مثل Babel ).

دع الدالات تقبل الاسترجاعات

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

في مثال السؤال ، يمكنك جعل الأمر foo يقبل معاودة الاتصال واستخدامه بمثابة معاودة اتصال success . إذا هذا

var result = foo();
// Code that depends on 'result'

يصبح

foo(function(result) {
    // Code that depends on 'result'
});

هنا قمنا بتعريف الدالة "inline" ولكن يمكنك تمرير أي مرجع دالة:

function myCallback(result) {
    // Code that depends on 'result'
}

foo(myCallback);

يتم تعريف foo نفسها على النحو التالي:

function foo(callback) {
    $.ajax({
        // ...
        success: callback
    });
}

سيشير الرجوع إلى الوظيفة التي foo إلى foo عندما نسميها foo ببساطة إلى success . بمعنى أنه بمجرد نجاح طلب Ajax ، $.ajax باستدعاء callback وتمرير الاستجابة إلى معاودة الاتصال (والتي يمكن الإشارة إليها result ، حيث أن هذه هي الطريقة التي حددنا بها رد الاتصال).

يمكنك أيضًا معالجة الاستجابة قبل تمريرها إلى معاودة الاتصال:

function foo(callback) {
    $.ajax({
        // ...
        success: function(response) {
            // For example, filter the response
            callback(filtered_response);
        }
    });
}

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

ES2015 +: عود مع then()

تعد then() ميزة جديدة في ECMAScript 6 (ES2015) ، ولكنها تتمتع بدعم جيد من المتصفح بالفعل. هناك أيضًا العديد من المكتبات التي تنفذ واجهة برمجة التطبيقات القياسية Promises وتوفر طرقًا إضافية لتسهيل استخدام وتكوين الوظائف غير المتزامنة (على سبيل المثال ، bluebird ).

الوعود هي حاويات للقيم المستقبلية . عندما يستلم الوعد القيمة (يتم حلها ) أو عندما يتم إلغاؤها ( مرفوض ) ، فإنه يقوم بإعلام جميع "المستمعين" الذين يرغبون في الوصول إلى هذه القيمة.

الميزة على الاستدعاءات العادية هي أنها تسمح لك بفك شفرتك وأن تكون أسهل في الإنشاء.

في ما يلي مثال بسيط لاستخدام وعد:

function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}

delay()
  .then(function(v) { // `delay` returns a promise
    console.log(v); // Log the value once it is resolved
  })
  .catch(function(v) {
    // Or do something else if it is rejected 
    // (it would not happen in this example, since `reject` is not called).
  });

بالتطبيق على مكالمة أجاكس لدينا ، يمكننا استخدام وعود مثل هذه:

function ajax(url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      resolve(this.responseText);
    };
    xhr.onerror = reject;
    xhr.open('GET', url);
    xhr.send();
  });
}

ajax("/echo/json")
  .then(function(result) {
    // Code depending on result
  })
  .catch(function() {
    // An error occurred
  });

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

مزيد من المعلومات حول الوعود: صخور HTML5 - وعود جافا سكريبت

ملاحظة جانبية: كائنات مؤجلة jQuery

الكائنات المؤجلة هي تنفيذ مخصص jQuery للوعود (قبل توحيد واجهة برمجة تطبيقات Promise). فهم يتصرفون كوعود تقريبًا ولكنهم يعرضون واجهة برمجة تطبيقات مختلفة قليلاً.

كل طريقة Ajax من jQuery تقوم بالفعل بإرجاع "كائن مؤجل" (في الواقع وعد للكائن المؤجل) والذي يمكنك فقط إرجاعه من وظيفتك:

function ajax() {
    return $.ajax(...);
}

ajax().done(function(result) {
    // Code depending on result
}).fail(function() {
    // An error occurred
});

ملاحظة جانبية: وعد gotchas

ضع في اعتبارك أن الوعود والكائنات المؤجلة ليست سوى حاويات لقيمة مستقبلية ، وليست القيمة نفسها. على سبيل المثال ، لنفترض أن لديك ما يلي:

function checkPassword() {
    return $.ajax({
        url: '/password',
        data: {
            username: $('#username').val(),
            password: $('#password').val()
        },
        type: 'POST',
        dataType: 'json'
    });
}

if (checkPassword()) {
    // Tell the user they're logged in
}

يفسر هذا الرمز المسائل غير السالفة المذكورة أعلاه. على وجه التحديد ، لا يقوم $.ajax() بتجميد الرمز أثناء فحص صفحة '/ كلمة المرور' على الخادم - فإنه يرسل طلبًا إلى الخادم وعندما ينتظر ، يقوم على الفور بإرجاع كائن jQuery Ajax مؤجل ، وليس الاستجابة من الخادم. هذا يعني أن العبارة if ستحصل دائمًا على هذا الكائن المؤجل ، وعلاجه على أنه true ، والمضي كما لو كان المستخدم قد قام بتسجيل الدخول.

لكن الإصلاح سهل:

checkPassword()
.done(function(r) {
    if (r) {
        // Tell the user they're logged in
    } else {
        // Tell the user their password was bad
    }
})
.fail(function(x) {
    // Tell the user something bad happened
});

غير مستحسن: مكالمات "Ajax" متزامنة

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

بدون jQuery

إذا كنت تستخدم كائن XMLHTTPRequest مباشرة ، بتمرير false كوسيطة ثالثة .open .

مسج

إذا كنت تستخدم jQuery ، فيمكنك تعيين خيار async على false . لاحظ أن هذا الخيار تم إيقافه منذ jQuery 1.8. يمكنك إما الاستمرار في استخدام رد اتصال success أو الوصول إلى خاصية responseText الخاصة بكائن jqXHR :

function foo() {
    var jqXHR = $.ajax({
        //...
        async: false
    });
    return jqXHR.responseText;
}

إذا كنت تستخدم أي طريقة jQuery Ajax أخرى ، مثل $.get ، $.getJSON ، وما إلى ذلك ، يجب عليك تغييرها إلى $.ajax (حيث يمكنك تمرير معلمات التكوين إلى $.ajax ).

انتباه! لا يمكن عمل طلب JSONP متزامن. JSONP بطبيعتها دائمًا غير متزامن (أحد الأسباب الأخرى لعدم النظر في هذا الخيار).


شبيبة هو واحد مترابطة.

يمكن تقسيم المتصفح إلى ثلاثة أجزاء:

1) حلقة الحدث

2) Web API

3) قائمة انتظار الحدث

يتم تشغيل حلقة الحدث للأبد أي نوع من lov.Event Queue هو المكان الذي يتم فيه دفع جميع وظائفك على بعض الأحداث (مثال: انقر) وهذا واحد تلو الآخر يتم تنفيذه من قائمة الانتظار ووضعه في حلقة الحدث التي تنفذ هذه الوظيفة وتعدها بنفسك للواحد التالي بعد تنفيذ أول واحد. ويعني هذا أن تنفيذ وظيفة لا يبدأ حتى يتم تنفيذ الوظيفة قبلها في قائمة الانتظار في حلقة الحدث.

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

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

وبالتالي الحل لهذا هو رد أو الوعد .

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

 function doAjax(callbackFunc, method, url) {
  var xmlHttpReq = new XMLHttpRequest();
  xmlHttpReq.open(method, url);
  xmlHttpReq.onreadystatechange = function() {

      if (xmlHttpReq.readyState == 4 && xmlHttpReq.status == 200) {
        callbackFunc(xmlHttpReq.responseText);
      }


  }
  xmlHttpReq.send(null);

}

في بلدي قانون يطلق عليه باسم

function loadMyJson(categoryValue){
  if(categoryValue==="veg")
  doAjax(print,"GET","http://localhost:3004/vegetables");
  else if(categoryValue==="fruits")
  doAjax(print,"GET","http://localhost:3004/fruits");
  else 
  console.log("Data not found");
}

اقرأ هنا لطرق جديدة في ECMA (2016/17) لإجراء الاتصال المتزامن (Felix Kling Answer on Top) https://.com/a/14220323/7579856


إذا كنت تستخدم الوعود ، فإن هذه الإجابة لك.

وهذا يعني AngularJS ، jQuery (مع مؤجل) ، استبدال XHR الأصلي (إحضار) ، EmberJS ، BackboneJS حفظ أو أي مكتبة العقدة التي تعيد الوعود.

يجب أن يكون رمزك شيئًا على غرار ما يلي:

function foo() {
    var data;
    // or $.get(...).then, or request(...).then, or query(...).then
    fetch("/echo/json").then(function(response){
        data = response.json();
    });
    return data;
}

var result = foo(); // result is always undefined no matter what.

قام Felix Kling بعمل جيد كتابة إجابة للأشخاص الذين يستخدمون jQuery مع الاستدعاءات لـ AJAX. لدي إجابة ل XHR الأصلي. هذه الإجابة للاستخدام العام للوعود سواء على الواجهة الأمامية أو الخلفية.

القضية الأساسية

نموذج التزامن JavaScript في المستعرض وعلى الخادم مع NodeJS / io.js غير متزامن ومتفاعل.

كلما قمت باستدعاء طريقة تعيد الوعد ، يتم دائماً تنفيذ معالجات then بشكل غير متزامن - أي بعد التعليمة البرمجية الموجودة أسفلها التي ليست في معالج .then .

هذا يعني أنك عندما تعيد data ، لم يقم المعالج الذي قمت بتعريفه بعد بتنفيذه. وهذا بدوره يعني أن القيمة التي تعيدها لم يتم ضبطها على القيمة الصحيحة في الوقت المناسب.

هنا تشبيه بسيط للقضية:

    function getFive(){
        var data;
        setTimeout(function(){ // set a timer for one second in the future
           data = 5; // after a second, do this
        }, 1000);
        return data;
    }
    document.body.innerHTML = getFive(); // `undefined` here and not 5

قيمة data غير undefined لأن data = 5 جزء لم تنفذ بعد. من المحتمل أن يتم تنفيذه في ثانية ولكن في ذلك الوقت لا يهم القيمة المرتجعة.

نظرًا لأن العملية لم تحدث بعد (AJAX ، أو استدعاء الخادم ، أو IO ، أو المؤقت) ، فستُرجع القيمة قبل أن يحصل الطلب على فرصة لإخبار الشفرة بتلك القيمة.

يتمثل أحد الحلول الممكنة لهذه المشكلة في إعادة التهيئة بشكل نشط ، وإخبار البرنامج بما يجب القيام به عند اكتمال الحساب. وعود تمكِّن هذا بشكل فعال من خلال كونها مؤقتة (حساسة للوقت) بطبيعتها.

تلخيص سريع للوعود

الوعد هو قيمة مع مرور الوقت . الوعود لديها دولة ، فإنها تبدأ في حالة انتظار بلا قيمة ويمكن أن تستقر في:

  • الوفاء بمعنى أن الحساب اكتمل بنجاح.
  • رفض يعني أن فشل الحساب.

الوعد لا يمكن أن يغير الدول إلا بعد ذلك سوف يبقى دائمًا في نفس الحالة إلى الأبد. يمكنك إرفاق then معالجات إلى الوعود لاستخراج قيمتها ومعالجة الأخطاء. then معالجات تسمح chaining المكالمات. يتم إنشاء الوعود باستخدام واجهات برمجة التطبيقات التي تعرضها . على سبيل المثال ، fetch AJAX أكثر حداثة أو jQuery's $.get وعود.

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

مع وعود

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

function delay(ms){ // takes amount of milliseconds
    // returns a new promise
    return new Promise(function(resolve, reject){
        setTimeout(function(){ // when the time is up
            resolve(); // change the promise to the fulfilled state
        }, ms);
    });
}

الآن ، بعد أن قمنا بتحويل setTimeout لاستخدام الوعود ، يمكننا استخدامه thenلجعله يحسب:

function delay(ms){ // takes amount of milliseconds
  // returns a new promise
  return new Promise(function(resolve, reject){
    setTimeout(function(){ // when the time is up
      resolve(); // change the promise to the fulfilled state
    }, ms);
  });
}

function getFive(){
  // we're RETURNING the promise, remember, a promise is a wrapper over our value
  return delay(100).then(function(){ // when the promise is ready
      return 5; // return the value 5, promises are all about return values
  })
}
// we _have_ to wrap it like this in the call site, we can't access the plain value
getFive().then(function(five){ 
   document.body.innerHTML = five;
});

في الأساس، وبدلا من إرجاع القيمة التي لا نستطيع أن نفعل ذلك لأن من طراز التزامن - نحن إرجاع المجمع بقيمة نتمكن من بسط مع then. إنها مثل مربع يمكنك فتحه then.

تطبيق هذا

هذا هو نفسه بالنسبة لمكالمة API الأصلية ، يمكنك:

function foo() {
    // RETURN the promise
    return fetch("/echo/json").then(function(response){
        return response.json(); // process it inside the `then`
    });
}

foo().then(function(response){
    // access the value inside the `then`
})

لذلك هذا يعمل كذلك. لقد تعلمنا أنه لا يمكننا إرجاع قيم من المكالمات غير المتزامنة بالفعل ولكن يمكننا استخدام الوعود وتسلسلها لتنفيذ المعالجة. نحن نعرف الآن كيفية إرجاع الاستجابة من مكالمة غير متزامنة.

ES2015 (ES6)

يقدم ES6 generators التي هي وظائف يمكن أن تعود في الوسط ثم تستأنف النقطة التي كانت عليها. هذا مفيد عادة للتسلسلات ، على سبيل المثال:

function* foo(){ // notice the star, this is ES6 so new browsers/node/io only
    yield 1;
    yield 2;
    while(true) yield 3;
}

هي دالة تقوم بإرجاع مكرر عبر التسلسل 1,2,3,3,3,3,....الذي يمكن تكراره. في حين أن هذا مثير للاهتمام من تلقاء نفسها ويفتح مجالاً لكثير من الاحتمالات ، هناك حالة واحدة مثيرة للاهتمام.

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

هذه الخدعة صعبة للغاية ولكن قوية جدا تتيح لنا كتابة رمز غير متزامن بطريقة متزامن. هناك العديد من "المتسابقين" الذين يقومون بذلك نيابة عنك ، فالكتابة هي عبارة عن بضعة أسطر قصيرة من الشفرة ولكنها خارج نطاق هذه الإجابة. سأستخدم بلوبيرد Promise.coroutineهنا ، ولكن هناك أغلفة أخرى مثل coأو Q.async.

var foo = coroutine(function*(){
    var data = yield fetch("/echo/json"); // notice the yield
    // code here only executes _after_ the request is done
    return data.json(); // data is defined
});

هذا الأسلوب يعيد الوعد نفسه ، والذي يمكننا أن نستهلكه من الكورنات الأخرى. فمثلا:

var main = coroutine(function*(){
   var bar = yield foo(); // wait our earlier coroutine, it returns a promise
   // server call done here, code below executes when done
   var baz = yield fetch("/api/users/"+bar.userid); // depends on foo's result
   console.log(baz); // runs after both requests done
});
main();

ES2016 (ES7)

في ES7 ، هذا أكثر توحيدًا ، هناك العديد من الاقتراحات الآن ولكن في كل منها يمكنك awaitالوعد. هذا هو مجرد "السكر" (جملة أجمل) للاقتراح ES6 فوق بإضافة asyncو awaitالكلمات الرئيسية. جعل المثال أعلاه:

async function foo(){
    var data = await fetch("/echo/json"); // notice the await
    // code here only executes _after_ the request is done
    return data.json(); // data is defined
}

انها لا تزال تعيد وعد فقط نفس :)


الإجابة 2017: يمكنك الآن القيام بما تريده بالضبط في كل متصفح وعقدة حالية

هذا بسيط للغاية:

  • أعد الوعد
  • استخدم await ، الذي سيخبر جافا سكريبت بانتظار الوعد ليتم حله في قيمة (مثل استجابة HTTP)
  • أضف الكلمة الأساسية async/await إلى الوظيفة الرئيسية

إليك إصدار يعمل من شفرتك:

(async function(){

var response = await superagent.get('...')
console.log(response)

})()

تنتظر المدعومة في جميع المتصفحات الحالية والعقدة 8


فيما يلي بعض الطرق للعمل مع الطلبات غير المتزامنة:

  1. then()
  2. Q - مكتبة الوعد لجافا سكريبت
  3. A + Promises.js
  4. jQuery مؤجل
  5. XMLHttpRequest API
  6. استخدام مفهوم معاودة الاتصال - كتطبيق في الإجابة الأولى

مثال: jQuery deferred implementation للعمل مع طلبات متعددة

var App = App || {};

App = {
    getDataFromServer: function(){

      var self = this,
                 deferred = $.Deferred(),
                 requests = [];

      requests.push($.getJSON('request/ajax/url/1'));
      requests.push($.getJSON('request/ajax/url/2'));

      $.when.apply(jQuery, requests).done(function(xhrResponse) {
        return deferred.resolve(xhrResponse.result);
      });
      return deferred;
    },

    init: function(){

        this.getDataFromServer().done(_.bind(function(resp1, resp2) {

           // Do the operations which you wanted to do when you
           // get a response from Ajax, for example, log response.
        }, this));
    }
};
App.init();


XMLHttpRequest 2 (أولاً وقبل كل قراءة الإجابات من بنجامين Gruenbaum & Felix Kling)

إذا كنت لا تستخدم jQuery وتريد الحصول على XMLHttpRequest 2 قصيرًا جيدًا يعمل على المتصفحات الحديثة وأيضًا على متصفحات الجوال التي أقترح استخدامها بهذه الطريقة:

function ajax(a, b, c){ // URL, callback, just a placeholder
  c = new XMLHttpRequest;
  c.open('GET', a);
  c.onload = b;
  c.send()
}

كما ترى:

  1. انها أقصر من جميع الوظائف الأخرى المدرجة.
  2. يتم تعيين رد الاتصال مباشرةً (لذلك لا يوجد إغلاق إضافي غير ضروري).
  3. ويستخدم onload الجديد (لذلك لم يكن لديك للتحقق من حالة readystate &&)
  4. هناك بعض الحالات الأخرى التي لا أتذكر أن تجعل XMLHttpRequest 1 مزعج.

هناك طريقتان للحصول على استجابة مكالمة Ajax هذه (ثلاثة تستخدم اسم var: XMLHttpRequest var):

الابسط:

this.response

أو إذا قمت لسبب ما bind() معاودة الاتصال بفئة:

e.target.response

مثال:

function callback(e){
  console.log(this.response);
}
ajax('URL', callback);

أو (واحد أعلاه هو أفضل وظائف مجهولة هي دائما مشكلة):

ajax('URL', function(e){console.log(this.response)});

لا شيء أسهل.

الآن قد يقول بعض الأشخاص أنه من الأفضل استخدام onreadystatechange أو حتى اسم المتغير XMLHttpRequest. ذلك خطأ.

تحقق من XMLHttpRequest الميزات المتقدمة

دعم على جميع المتصفحات الحديثة. ويمكن أن أؤكد أنني أستخدم هذا الأسلوب حيث يوجد XMLHttpRequest 2. لم يكن لدي أي نوع من المشاكل على جميع المتصفحات التي أستخدمها.

يكون onreadystatechange مفيدًا فقط إذا كنت تريد الحصول على العناوين في الحالة 2.

استخدام اسم المتغير XMLHttpRequest هو خطأ كبير آخر حيث تحتاج إلى تنفيذ رد الاتصال داخل إغلاق onload / oreadystatechange وإلا فقدته.

الآن إذا كنت تريد شيئًا أكثر تعقيدًا باستخدام النشر و FormData ، يمكنك بسهولة توسيع هذه الوظيفة:

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val},placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.send(d||null)
}

مرة أخرى ... إنها وظيفة قصيرة جدًا ، ولكنها تحصل على & مشاركة.

أمثلة على الاستخدام:

x(url, callback); // By default it's get so no need to set
x(url, callback, 'post', {'key': 'val'}); // No need to set post data

أو تمرير عنصر نموذج كامل ( document.getElementsByTagName('form')[0] ):

var fd = new FormData(form);
x(url, callback, 'post', fd);

أو قم بتعيين بعض القيم المخصصة:

var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);

كما ترى ، لم أقم بتطبيق المزامنة ... إنه أمر سيئ.

وقد قلت ذلك ... لماذا لا تفعل ذلك بالطريقة السهلة؟

كما ذكر في التعليق ، فإن استخدام الخطأ && synchronous ينهي تماما نقطة الإجابة. ما هي طريقة قصيرة لطيفة لاستخدام Ajax بالطريقة الصحيحة؟

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

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val}, placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.onerror = error;
  c.send(d||null)
}

function error(e){
  console.log('--Error--', this.type);
  console.log('this: ', this);
  console.log('Event: ', e)
}
function displayAjax(e){
  console.log(e, this);
}
x('WRONGURL', displayAjax);

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

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

قد تكون معالجات الخطأ مفيدة إذا قمت بتعيين رؤوس مخصصة ، قم بتعيين responseType على مخزن الصفيف blob أو أياً كان ....

حتى إذا قمت بتمرير "POSTAPAPAP" كطريقة لن تعرض خطأ.

حتى إذا قمت بتمرير "fdggdgilfdghfldj" كصورة نموذجية ، فلن يرتكب خطأ.

في الحالة الأولى يكون الخطأ داخل displayAjax() ضمن this.statusText كطريقة Method not Allowed .

في الحالة الثانية ، تعمل ببساطة. يجب عليك التحقق من جانب الخادم إذا قمت بتمرير بيانات النشر المناسبة.

عبر المجال لا يسمح بالرمي الخطأ تلقائيا.

في استجابة الخطأ ، لا توجد رموز خطأ.

لا يوجد سوى نوع this.type الذي تم تعيينه على الخطأ.

لماذا تضيف معالج أخطاء إذا لم يكن لديك أي تحكم في الأخطاء؟ يتم إرجاع معظم الأخطاء داخل هذا في وظيفة رد الاتصال displayAjax() .

لذلك: لا حاجة لفحص الأخطاء إذا كنت قادرًا على نسخ عنوان URL ولصقه بشكل صحيح. ؛)

ملاحظة: كاختبار الأول كتبت x ('x' ، displayAjax) ... ، وحصلت على رد تماما ... ؟؟؟ لذلك ، تحققت من المجلد حيث يوجد HTML ، وكان هناك ملف يسمى "x.xml". لذلك حتى إذا كنت قد نسيت امتداد الملف الخاص بك XMLHttpRequest 2 سوف تجد ذلك . أنا LOL'd

قراءة ملف متزامن

لا تفعل هذا

إذا كنت ترغب في منع المتصفح لفترة من الوقت تحميل ملف كبير txt كبيرة متزامن.

function omg(a, c){ // URL
  c = new XMLHttpRequest;
  c.open('GET', a, true);
  c.send();
  return c; // Or c.response
}

الآن يمكنك القيام به

 var res = omg('thisIsGonnaBlockThePage.txt');

لا توجد طريقة أخرى للقيام بذلك بطريقة غير متزامنة. (نعم ، مع حلقة setTimeout ... ولكن على محمل الجد؟)

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

فقط إذا كان لديك صفحة يتم فيها تحميل نفس XML / JSON أو أيًا كان ما تحتاجه وظيفة واحدة فقط. في هذه الحالة ، قم بتعديل وظيفة Ajax واستبدل b بخاصتك الخاصة.

الوظائف المذكورة أعلاه هي للاستخدام الأساسي.

إذا كنت ترغب في تمديد الوظيفة ...

نعم تستطيع.

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

ولكن يمكنك القيام بالكثير من الأشياء باستخدام XMLHttpRequest 2:

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

لكن السؤال هنا هو كيفية إرجاع رد أياكس ... (أضفت طريقة سهلة).


إلقاء نظرة على هذا المثال:

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope,$http) {

    var getJoke = function(){
        return $http.get('http://api.icndb.com/jokes/random').then(function(res){
            return res.data.value;  
        });
    }

    getJoke().then(function(res) {
        console.log(res.joke);
    });
});

كما ترون getJokeو إرجاع حل الوعد (يتم حلها عند عودته res.data.value). حتى تنتظر حتى يتم إكمال طلب http.get $ ثم يتم تنفيذ console.log (res.joke) (كتدفق غير متزامن عادي).

هذا هو plnkr:

http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/


دعونا نرى الغابة أولا قبل النظر إلى الأشجار.

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

  1. يتم تنفيذ نقطة الإدخال (نقاطك) كنتيجة لحدث ما. على سبيل المثال ، يتم تحميل علامة البرنامج النصي مع التعليمات البرمجية في المستعرض. (وبناءً على ذلك ، هذا هو السبب في أنك قد تحتاج إلى الاهتمام باستعداد الصفحة لتشغيل شفرتك إذا كانت تتطلب إنشاء عناصر dom أولاً ، إلخ.)
  2. ينفذ الكود الخاص بك حتى اكتماله - على الرغم من العديد من المكالمات غير المتزامنة التي يقوم بها - دون تنفيذ أي من الاستدعاءات الخاصة بك ، بما في ذلك طلبات XHR ، تعيين المهلات ، معالجات أحداث dom ، إلخ. كل من تلك الاستدعاءات التي تنتظر التنفيذ سيتم وضعها في قائمة الانتظار ، في الانتظار دورهم ليتم تشغيلها بعد الأحداث الأخرى التي أطلقت قد انتهى جميع التنفيذ.
  3. عندئذٍ ، سيتم تشغيل كل رد اتصال فردي لطلب XHR أو تعيين المهلة أو dom الحدث الذي تم استدعاؤه مرة أخرى حتى اكتماله.

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

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


المثال التالي الذي كتبته يظهر كيف

  • التعامل مع مكالمات HTTP غير المتزامنة ؛
  • انتظر الرد من كل مكالمة API ؛
  • استخدام نمط Promise ؛
  • استخدم نمط Promise.All للانضمام إلى مكالمات HTTP متعددة ؛

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

سياق الكلام. المثال هو الاستعلام عن نقطة نهاية API الخاصة بـ Spotify Web من أجل البحث عن playlistكائنات لمجموعة معينة من سلاسل الاستعلام:

[
 "search?type=playlist&q=%22doom%20metal%22",
 "search?type=playlist&q=Adele"
]

لكل عنصر ، سيقوم Promise جديد بإطلاق كتلة - ExecutionBlock، تحليل النتائج ، جدولة مجموعة جديدة من الوعود استنادًا إلى مصفوفة النتائج ، وهي قائمة userبكائنات Spotify وتنفيذ استدعاء HTTP الجديد داخل ExecutionProfileBlockغير متزامن.

يمكنك بعد ذلك رؤية بنية Promation متداخلة ، تتيح لك إنتاج استدعاءات HTTP متداخلة متعددة وغير متزامنة تمامًا ، والانضمام إلى النتائج من كل مجموعة فرعية من المكالمات خلال Promise.all.

ملاحظة:search ستحتاج واجهات برمجة تطبيقات Spotify الأخيرة إلى رمز وصول يمكن تحديده في رؤوس الطلبات:

-H "Authorization: Bearer {your access token}" 

لذلك ، لتشغيل المثال التالي ، يجب وضع رمز الدخول الخاص بك في رؤوس الطلبات:

var spotifyAccessToken = "YourSpotifyAccessToken";
var console = {
    log: function(s) {
        document.getElementById("console").innerHTML += s + "<br/>"
    }
}

// Simple XMLHttpRequest
// based on https://davidwalsh.name/xmlhttprequest
SimpleRequest = {
    call: function(what, response) {
        var request;
        if (window.XMLHttpRequest) { // Mozilla, Safari, ...
            request = new XMLHttpRequest();
        } else if (window.ActiveXObject) { // Internet Explorer
            try {
                request = new ActiveXObject('Msxml2.XMLHTTP');
            }
            catch (e) {
                try {
                  request = new ActiveXObject('Microsoft.XMLHTTP');
                } catch (e) {}
            }
        }

        // State changes
        request.onreadystatechange = function() {
            if (request.readyState === 4) { // Done
                if (request.status === 200) { // Complete
                    response(request.responseText)
                }
                else
                    response();
            }
        }
        request.open('GET', what, true);
        request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken);
        request.send(null);
    }
}

//PromiseAll
var promiseAll = function(items, block, done, fail) {
    var self = this;
    var promises = [],
                   index = 0;
    items.forEach(function(item) {
        promises.push(function(item, i) {
            return new Promise(function(resolve, reject) {
                if (block) {
                    block.apply(this, [item, index, resolve, reject]);
                }
            });
        }(item, ++index))
    });
    Promise.all(promises).then(function AcceptHandler(results) {
        if (done) done(results);
    }, function ErrorHandler(error) {
        if (fail) fail(error);
    });
}; //promiseAll

// LP: deferred execution block
var ExecutionBlock = function(item, index, resolve, reject) {
    var url = "https://api.spotify.com/v1/"
    url += item;
    console.log( url )
    SimpleRequest.call(url, function(result) {
        if (result) {

            var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) {
                return item.owner.href;
            })
            resolve(profileUrls);
        }
        else {
            reject(new Error("call error"));
        }
    })
}

arr = [
    "search?type=playlist&q=%22doom%20metal%22",
    "search?type=playlist&q=Adele"
]

promiseAll(arr, function(item, index, resolve, reject) {
    console.log("Making request [" + index + "]")
    ExecutionBlock(item, index, resolve, reject);
}, function(results) { // Aggregated results

    console.log("All profiles received " + results.length);
    //console.log(JSON.stringify(results[0], null, 2));

    ///// promiseall again

    var ExecutionProfileBlock = function(item, index, resolve, reject) {
        SimpleRequest.call(item, function(result) {
            if (result) {
                var obj = JSON.parse(result);
                resolve({
                    name: obj.display_name,
                    followers: obj.followers.total,
                    url: obj.href
                });
            } //result
        })
    } //ExecutionProfileBlock

    promiseAll(results[0], function(item, index, resolve, reject) {
        //console.log("Making request [" + index + "] " + item)
        ExecutionProfileBlock(item, index, resolve, reject);
    }, function(results) { // aggregated results
        console.log("All response received " + results.length);
        console.log(JSON.stringify(results, null, 2));
    }

    , function(error) { // Error
        console.log(error);
    })

    /////

  },
  function(error) { // Error
      console.log(error);
  });
<div id="console" />

لقد ناقشت هذا الحل على نطاق واسع here .


باستخدام ES2017 يجب أن يكون هذا بمثابة إعلان الدالة

async function foo() {
    var response = await $.ajax({url: '...'})
    return response;
}

وتنفيذها مثل هذا.

(async function() {
    try {
        var result = await foo()
        console.log(result)
    } catch (e) {}
})()

أو بناء الجملة وعد

foo().then(response => {
    console.log(response)

}).catch(error => {
    console.log(error)

})

كان السؤال:

كيف أعيد الاستجابة من مكالمة غير متزامنة؟

والتي يمكن تفسيرها على النحو التالي:

كيفية جعل رمز غير متزامن تبدو متزامنة ؟

ويكمن الحل في تجنب الاسترجاعات، واستخدام مزيج من وعود و المتزامن / ننتظر .

أود أن أعطي مثالا لطلب أياكس.

(على الرغم من أنه يمكن كتابتها في Javascript ، إلا أنني أفضل كتابتها في Python ، Transcrypt بتجميعها إلى Javascript باستخدام Transcrypt . سيكون واضحًا بما فيه الكفاية.)

يتيح أولاً تمكين استخدام JQuery ، لتتوفر على $النحو التالي S:

__pragma__ ('alias', 'S', '$')

قم بتعريف دالة تقوم بإرجاع Promise ، وفي هذه الحالة استدعاء Ajax:

def read(url: str):
    deferred = S.Deferred()
    S.ajax({'type': "POST", 'url': url, 'data': { },
        'success': lambda d: deferred.resolve(d),
        'error': lambda e: deferred.reject(e)
    })
    return deferred.promise()

استخدم الشفرة غير المتزامنة كما لو كانت متزامنة :

async def readALot():
    try:
        result1 = await read("url_1")
        result2 = await read("url_2")
    except Exception:
        console.warn("Reading a lot failed")

يحتوي ECMAScript 6 على "مولدات" تسمح لك بالبرمجة بسهولة بأسلوب غير متزامن.

function* myGenerator() {
    const callback = yield;
    let [response] = yield $.ajax("https://.com", {complete: callback});
    console.log("response is:", response);

    // examples of other things you can do
    yield setTimeout(callback, 1000);
    console.log("it delayed for 1000ms");
    while (response.statusText === "error") {
        [response] = yield* anotherGenerator();
    }
}

لتشغيل التعليمات البرمجية أعلاه ، يمكنك القيام بذلك:

const gen = myGenerator(); // Create generator
gen.next(); // Start it
gen.next((...args) => gen.next([...args])); // Set its callback function

إذا كنت بحاجة لاستهداف متصفحات لا تدعم ES6 ، يمكنك تشغيل الشفرة من خلال Babel أو برنامج التحويل الإغلاق لإنشاء ECMAScript 5.

...argsيتم التفاف رد الاتصال في صفيف و destructured عند قراءتها بحيث يمكن التعامل مع نمط الاسترجاعات التي تحتوي على وسائط متعددة. على سبيل المثال مع عقدة fs :

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);

أبسط حل هو إنشاء وظيفة جافا سكريبت واستدعاءها لاستدعاء Ajax success.

function callServerAsync(){
    $.ajax({
        url: '...',
        success: function(response) {

            successCallback(response);
        }
    });
}

function successCallback(responseObj){
    // Do something like read the response and show data
    alert(JSON.stringify(responseObj)); // Only applicable to JSON response
}

function foo(callback) {

    $.ajax({
        url: '...',
        success: function(response) {
           return callback(null, response);
        }
    });
}

var result = foo(function(err, result){
          if (!err)
           console.log(result);    
}); 

إنها مشكلة شائعة جدًا نواجهها بينما نكافح مع "أسرار" جافا سكريبت. دعني أحاول إزالة الغموض عن هذا اللغز اليوم.

لنبدأ بدالة JavaScript بسيطة:

function foo(){
// do something 
 return 'wohoo';
}

let bar = foo(); // bar is 'wohoo' here

وهي عبارة عن استدعاء وظيفي متزامن بسيط (حيث يتم "إنهاء" كل سطر من الشفرات مع وظيفته قبل التتابع التالي) ، وتكون النتيجة كما هو متوقع.

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

function foo(){
 setTimeout( ()=>{
   return 'wohoo';
  }, 1000 )
}

let bar = foo() // bar is undefined here

إذن فهناك ، هذا التأخير حطم الوظيفة التي توقعناها! لكن ما الذي حدث بالضبط؟ حسنًا ، إنها منطقية جدًا إذا نظرت إلى الشفرة. الدالة foo()، عند التنفيذ ، إرجاع أي شيء (وبالتالي القيمة التي تم إرجاعها undefined) ، ولكنه يبدأ جهاز ضبط وقت ، الذي ينفذ دالة بعد 1s للعودة "wohoo". ولكن كما ترى ، القيمة التي يتم تعيينها إلى الشريط هي الأشياء التي تم إرجاعها فورًا من foo () ، وليس أي شيء آخر يأتي لاحقًا.

إذن كيف نتعامل مع هذه القضية؟

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

function foo(){
   return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
    setTimeout ( function(){ 
      // promise is RESOLVED , when execution reaches this line of code
       resolve('wohoo')// After 1 second, RESOLVE the promise with value 'wohoo'
    }, 1000 )
  })
}

let bar ; 
foo().then( res => {
 bar = res;
 console.log(bar) // will print 'wohoo'
});

وبالتالي ، فإن الملخص هو - لمعالجة الوظائف غير المتزامنة مثل المكالمات القائمة على ajax إلخ ، يمكنك استخدام وعد resolveبالقيمة (التي تنوي إرجاعها). وبالتالي ، باختصار يمكنك حل القيمة بدلاً من العودة ، في وظائف غير متزامنة.

تحديث (وعود مع async / في انتظار)

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

ثم / نسخة الصيد:

function fetchUsers(){
   let users = [];
   getUsers()
   .then(_users => users = _users)
   .catch(err =>{
      throw err
   })
   return users;
 }

إصدار async / await:

  async function fetchUsers(){
     try{
        let users = await getUsers()
        return users;
     }
     catch(err){
        throw err;
     }
  }

الجواب باختصار هو ، يجب عليك تنفيذ رد اتصال مثل هذا:

function callback(response) {
    // Here you can do what ever you want with the response object.
    console.log(response);
}

$.ajax({
    url: "...",
    success: callback
});

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

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            myCallback(response);
        }
    });

    return result;
}

function myCallback(response) {
    // Does something.
}

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

// WRONG
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.push(result);
    });
});
console.log(results); // E.g., using them, returning them, etc.

مثال:

// WRONG
var theArray = [1, 2, 3];
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.push(result);
    });
});
console.log("Results:", results); // E.g., using them, returning them, etc.

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

السبب الذي لا يعمل هو أن عمليات الاسترجاع doSomethingAsyncلم يتم تشغيلها بعد في الوقت الذي تحاول فيه استخدام النتائج.

لذا ، إذا كان لديك صفيف (أو قائمة من نوع ما) وتريد القيام بعمليات المتزامن لكل إدخال ، فلديك خياران: هل العمليات متوازية (متداخلة) ، أو في سلسلة (واحدة تلو الأخرى في التسلسل).

موازى

يمكنك بدء كل منهم وتتبع عدد عمليات الاسترداد التي تتوقعها ، ثم استخدام النتائج عندما تحصل على العديد من عمليات الرد:

var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", results); // E.g., using the results
        }
    });
});

مثال:

var theArray = [1, 2, 3];
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", results); // E.g., using the results
        }
    });
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(يمكن أن نتخلص منه ونستخدمه expectingفقط results.length === theArray.length، ولكن هذا يتركنا theArrayعرضة للإمكانية التي تتغير عندما تكون المكالمات معلقة ...)

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

ولكن ماذا لو كنت بحاجة إلى إرجاع هذه النتائج من وظيفة؟ كما أوضحت الإجابات الأخرى ، لا يمكنك ذلك. يجب أن تقبل وظيفتك وتعيد الاتصال بك (أو تعيد then() ). إليك إصدار معاودة الاتصال:

function doSomethingWith(theArray, callback) {
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                callback(results);
            }
        });
    });
}
doSomethingWith(theArray, function(results) {
    console.log("Results:", results);
});

مثال:

function doSomethingWith(theArray, callback) {
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                callback(results);
            }
        });
    });
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

أو هنا إصدارًا يعرض Promiseبدلاً من ذلك:

function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

بالطبع ، إذا doSomethingAsyncمرت علينا أخطاء ، فسنستخدمها rejectلرفض الوعد عندما حصل خطأ.)

مثال:

function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(أو بالتناوب ، يمكنك عمل غلاف doSomethingAsyncلإرجاع وعد ، ثم قم بما يلي أدناه)

إذا doSomethingAsyncمنحك then() ، فيمكنك استخدام Promise.all:

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(function(entry) {
        return doSomethingAsync(entry, function(result) {
            results.push(result);
        });
    }));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

مثال:

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(function(entry) {
        return doSomethingAsync(entry, function(result) {
            results.push(result);
        });
    }));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

لاحظ أن Promise.allحل وعدها مع مجموعة من نتائج جميع الوعود التي قدمتها عند حلها جميعا ، أو رفض وعدها عندما رفضت الوعود الأولى من الوعود التي تعطيها.

سلسلة

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

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith(theArray, function(results) {
    console.log("Results:", results);
});

(بما أننا ننفذ العمل في سلسلة ، فيمكننا استخدامه فقط results.push(result)لأننا نعلم أننا لن نحصل على نتائج بدون ترتيب. في ما سبق ، كان يمكن أن نستخدمها results[index] = result;، ولكن في بعض الأمثلة التالية ليس لدينا فهرس ليستخدم.)

مثال:

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(أو ، مرة أخرى ، قم ببناء غلاف doSomethingAsyncلذلك يعطيك وعدًا وقم بما يلي أدناه ...)

إذا doSomethingAsyncيمنحك وعد، إذا كان يمكنك استخدام ES2017 + تركيب (ربما مع transpiler مثل بابل )، يمكنك استخدام async/await مع for-ofو await:

async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith(theArray).then(results => {
    console.log("Results:", results);
});

مثال:

async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

إذا لم تتمكن من استخدام بنية ES2017 + (حتى الآن) ، فيمكنك استخدام صيغة مختلفة على نموذج "تقليل الإعداء " (وهذا أكثر تعقيدًا من الوعد المعتاد للتقليل لأننا لا نمر بالنتيجة من واحدة إلى أخرى ، ولكن بدلاً من ذلك جمع نتائجهم في مجموعة):

function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

مثال:

function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

... وهو أقل تعقيدًا مع وظائف ES2015 + السهم :

function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith(theArray).then(results => {
    console.log("Results:", results);
});

مثال:

function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}


حل آخر هو تنفيذ التعليمات البرمجية عبر nsynjs المنفذ تسلسلي .

إذا تم تبني وظيفة الكامنة

سيقيّم nsynjs جميع الوعود بالتتابع ، ويضع نتائج واعدة في dataالملكية:

function synchronousCode() {

    var getURL = function(url) {
        return window.fetch(url).data.text().data;
    };
    
    var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
    console.log('received bytes:',getURL(url).length);
    
};

nsynjs.run(synchronousCode,{},function(){
    console.log('synchronousCode done');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>

إذا لم يتم القيام بالوظيفة الأساسية

الخطوة 1. وظيفة التفاف مع معاودة الاتصال في المجمع المدروس nsynjs (إذا كان لديها نسخة promisified ، يمكنك تخطي هذا الاختبار):

var ajaxGet = function (ctx,url) {
    var res = {};
    var ex;
    $.ajax(url)
    .done(function (data) {
        res.data = data;
    })
    .fail(function(e) {
        ex = e;
    })
    .always(function() {
        ctx.resume(ex);
    });
    return res;
};
ajaxGet.nsynjsHasCallback = true;

الخطوة 2. وضع منطق متزامن في الوظيفة:

function process() {
    console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);
}

الخطوة 3. تشغيل وظيفة بطريقة متزامن عبر nnsynjs:

nsynjs.run(process,this,function () {
    console.log("synchronous function finished");
});

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

مزيد من الأمثلة هنا: https://github.com/amaksr/nsynjs/tree/master/examples


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

هذا هو:

function handleData( responseData ) {

    // Do what you want with the data
    console.log(responseData);
}

$.ajax({
    url: "hi.php",
    ...
    success: function ( data, status, XHR ) {
        handleData(data);
    }
});

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


نجد أنفسنا في عالم يبدو أنه يتقدم على طول نطلق عليه "الوقت". لا نفهم حقاً ما هو الوقت ، لكننا طورنا التجريدات والمفردات التي سمحت لنا بالتفكير والتحدث عنها: "الماضي" ، "الحاضر" ، "المستقبل" ، "قبل" ، "بعد".

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

النظر في سبيل المثال. كنت اتصل الحليب وأمر بعض الحليب. عندما يأتي ، تريد وضعه في قهوتك. لا يمكنك وضع الحليب في قهوتك الآن ، لأنه ليس هنا بعد. عليك الانتظار حتى تأتي قبل وضعها في قهوتك. بمعنى آخر ، لن يعمل التالي:

var milk = order_milk();
put_in_coffee(milk);

لأن JS لا يوجد طريقة لمعرفة أنه يحتاج إلى الانتظار ل order_milkلإنهاء قبل أن ينفذ put_in_coffee. وبعبارة أخرى ، لا يعرف أنه order_milkغير متزامن - وهو شيء لن يؤدي إلى الحليب حتى بعض الوقت في المستقبل. تقوم JS وغيرها من اللغات التفصيلية بتنفيذ عبارة واحدة تلو الأخرى دون انتظار.

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

order_milk(put_in_coffee);

order_milkينطلق ، يأمر بالحليب ، عندما ، وعندما وعندما يأتي فقط ، فإنه يستدعي put_in_coffee.

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

order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }

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

في هذه الحالة ، يمكننا إعادة كتابة الشفرة في السؤال على النحو التالي:

var answer;
$.ajax('/foo.json') . done(function(response) {
  callback(response.data);
});

function callback(data) {
  console.log(data);
}

أدخل الوعود

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

في حالة الحليب والقهوة لدينا ، نصمم order_milkلإعادة الوعد بالحليب القادم ، ثم تحديده put_in_coffeeكعمل then، على النحو التالي:

order_milk() . then(put_in_coffee)

وتتمثل إحدى مزايا هذا في أنه يمكننا تجميع هذه السلاسل معاً لإنشاء تسلسلات الأحداث المستقبلية ("تسلسل"):

order_milk() . then(put_in_coffee) . then(drink_coffee)

دعونا تطبيق وعود لمشكلتك الخاصة. سنقوم بلف منطق الطلب الخاص بنا داخل إحدى الوظائف ، والتي تعيد الوعد:

function get_data() {
  return $.ajax('/foo.json');
}

في الواقع ، تمت إضافة كل ما قمنا به returnإلى الاتصال بـ $.ajax. هذا لأن jQuery $.ajaxيعود بالفعل إلى نوع من الوعد. (من الناحية العملية ، دون الخوض في التفاصيل ، فإننا نفضل أن نلف هذه الدعوة حتى نعيد وعدًا حقيقيًا ، أو نستخدم بديلاً للقيام $.ajaxبذلك). والآن ، إذا أردنا تحميل الملف والانتظار حتى ينتهي ، افعل شيئًا ، يمكننا القول ببساطة

get_data() . then(do_something)

على سبيل المثال،

get_data() . 
  then(function(data) { console.log(data); });

عند استخدام الوعود ، ينتهي بنا الأمر إلى تمرير العديد من الوظائف then، لذلك من المفيد غالباً استخدام وظائف الأسهم ذات النمط ES6 الأكثر مدمجة:

get_data() . 
  then(data => console.log(data));

و asyncالكلمة

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

a();
b();

ولكن إذا كان aغير متزامن ، مع وعود علينا أن نكتب

a() . then(b);

أعلاه ، قلنا "ليس لدى JS أي طريقة لمعرفة أنه يجب الانتظار حتى انتهاء المكالمة الأولى قبل تنفيذها الثانية". ألن يكون لطيفًا إذا كان هناك طريقة لإخبار JS بذلك؟ اتضح أن هناك - awaitالكلمة الأساسية ، تستخدم داخل نوع خاص من وظيفة تسمى وظيفة "المتزامن". هذه الميزة جزء من الإصدار القادم من ES ، ولكنها متوفرة بالفعل في برامج التحويل العام مثل Babel بالنظر إلى الإعدادات المسبقة الصحيحة. هذا يسمح لنا بالكتابة

async function morning_routine() {
  var milk   = await order_milk();
  var coffee = await put_in_coffee(milk);
  await drink(coffee);
}

في حالتك ، ستتمكن من كتابة شيء من هذا القبيل

async function foo() {
  data = await get_data();
  console.log(data);
}






ecmascript-2017