it-swarm.asia

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

لدي وظيفة 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`.
4904
Felix Kling

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

يجب أن يكون شفرتك وفقًا لما يلي:

function foo() {
    var httpRequest = new XMLHttpRequest();
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
    return httpRequest.responseText;
}

var result = foo(); // always ends up being 'undefined'

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

( ملاحظة ، لأولئك الذين يستخدمون fetch API الجديد ، Angular أو الوعود لقد أضفت إجابة أخرى أدناه )


ما كنت تواجه

هذا ملخص قصير لـ "شرح المشكلة" من الإجابة الأخرى ، إذا لم تكن متأكدًا بعد قراءة هذا ، فاقرأ ذلك.

Ain AJAX تعني غير متزامن . هذا يعني أن إرسال الطلب (أو بالأحرى تلقي الاستجابة) يتم إخراجه من تدفق التنفيذ العادي. في المثال الخاص بك ، .send تُرجع فورًا ويتم تنفيذ البيان التالي ، return result; ، قبل استدعاء الوظيفة التي مرت بها كـ success.

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

هنا تشبيه بسيط

function getFive(){ 
    var a;
    setTimeout(function(){
         a=5;
    },10);
    return a;
}

(كمان)

قيمة a التي تم إرجاعها هي undefined لأن الجزء a=5 لم ينفذ بعد. AJAX يتصرف مثل هذا ، فأنت تقوم بإرجاع القيمة قبل أن يحصل الخادم على فرصة لإخبار متصفحك عن هذه القيمة.

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

function onComplete(a){ // When the code completes, do this
    alert(a);
}

function getFive(whenDone){ 
    var a;
    setTimeout(function(){
         a=5;
         whenDone(a);
    },10);
}

وهذا ما يسمى CPS . في الأساس ، نحن نمرر getFive إجراءً يجب تنفيذه عند اكتماله ، ونخبر الكود الخاص بنا بكيفية الرد عند اكتمال الحدث (مثل مكالمة AJAX ، أو في هذه الحالة المهلة المحددة).

سيكون الاستخدام:

getFive(onComplete);

والتي يجب أن تنبه "5" إلى الشاشة. (كمان) .

الحلول الممكنة

هناك أساسا طريقتان لحل هذه المشكلة:

  1. اجعل AJAX اتصالًا متزامنًا (دعنا نسميه SJAX).
  2. إعادة هيكلة التعليمات البرمجية الخاصة بك للعمل بشكل صحيح مع عمليات الاسترجاعات.

1. متزامن AJAX - لا تفعل ذلك !!

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

يدعم XMLHttpRequest كلاً من الاتصالات المتزامنة وغير المتزامنة. بشكل عام ، ومع ذلك ، ينبغي تفضيل الطلبات غير المتزامنة على الطلبات المتزامنة لأسباب تتعلق بالأداء.

باختصار ، تمنع الطلبات المتزامنة تنفيذ التعليمات البرمجية ... ... هذا يمكن أن يسبب مشاكل خطيرة ...

إذا كنت لديك​​_ للقيام بذلك ، فيمكنك تمرير علامة: إليك كيفية القيام بذلك:

var request = new XMLHttpRequest();
request.open('GET', 'yourURL', false);  // `false` makes the request synchronous
request.send(null);

if (request.status === 200) {// That's HTTP for 'ok'
  console.log(request.responseText);
}

2. رمز إعادة الهيكلة

دع وظيفتك تقبل رد اتصال. في مثال التعليمة البرمجية foo يمكن إجراء قبول رد اتصال. سنخبر الكود الخاص بنا بكيفية رد فعل عند اكتمال foo.

وبالتالي:

var result = foo();
// code that depends on `result` goes here

يصبح:

foo(function(result) {
    // code that depends on `result`
});

لقد مررنا هنا وظيفة مجهولة المصدر ، لكننا نستطيع بسهولة تمرير إشارة إلى وظيفة موجودة ، مما يجعلها تبدو كما يلي:

function myHandler(result) {
    // code that depends on `result`
}
foo(myHandler);

لمزيد من التفاصيل حول كيفية إجراء هذا النوع من تصميم معاودة الاتصال ، تحقق من إجابة Felix.

الآن ، دعونا نحدد فو للعمل بنفسه

function foo(callback) {
    var httpRequest = new XMLHttpRequest();
    httpRequest.onload = function(){ // when the request is loaded
       callback(httpRequest.responseText);// we're calling our method
    };
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
}

(كمان)

لقد جعلنا الآن وظيفة foo لدينا تقبل إجراءً يتم تنفيذه عند اكتمال AJAX بنجاح ، يمكننا تمديد ذلك عن طريق التحقق مما إذا كانت حالة الاستجابة ليست 200 وتتصرف وفقًا لذلك (إنشاء معالج فشل وما شابه). حل فعال قضيتنا.

إذا كنت لا تزال تواجه صعوبة في فهم هذا اقرأ AJAX دليل البدء على MDN.

1002
Benjamin Gruenbaum

XMLHttpRequest 2 (بادئ ذي بدء ، اقرأ الإجابات من بنجامين جرونباوم & فيليكس كلينج )

إذا كنت لا تستخدم 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 الجديد (بحيث لا تضطر إلى التحقق من حالة && حالة القراءة)
  4. هناك بعض المواقف الأخرى التي لا أتذكرها والتي تجعل XMLHttpRequest 1 مزعجًا.

هناك طريقتان للحصول على استجابة استدعاء Ajax (ثلاث باستخدام اسم 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);

كما ترون ، لم أقم بتنفيذ المزامنة ... إنه أمر سيء.

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


كما هو مذكور في التعليق ، فإن استخدام الخطأ && التزامن لا يكسر تماما نقطة الإجابة. ما هي طريقة لطيفة قصيرة لاستخدام 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 array buffer أو أيًا كان ...

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

حتى إذا قمت بتمرير "fdggdgilfdghfldj" كـ formdata ، فلن يلقي خطأً.

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

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

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

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

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

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

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

PS: كأول اختبار كتبته x ('x' ، displayAjax) ... ، وحصلت تمامًا على استجابة ... ؟؟؟ لذلك راجعت المجلد حيث يوجد HTML ، وكان هناك ملف يسمى "x.xml" ، لذا حتى لو نسيت امتداد الملف XMLHttpRequest 2 WILL FIND IT. أنا سأكون كذلك


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

لا تفعل ذلك.

إذا كنت ترغب في حظر المتصفح لفترة من الوقت ، فقم بتحميل ملف .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 ... ولكن على محمل الجد؟)

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

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


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

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

نعم تستطيع.

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

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

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

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

363
cocco

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

هذا يعني AngularJS أو jQuery (مع تأجيل) أو استبدال XHR الأصلي (جلب) أو EmberJS أو BackboneJS save أو أي مكتبة عقدة تُرجع الوعود.

يجب أن يكون شفرتك وفقًا لما يلي:

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 ، لم يتم تنفيذ معالج then الذي حددته بعد. وهذا بدوره يعني أن القيمة التي تعود إليها لم يتم ضبطها على القيمة الصحيحة في الوقت المناسب.

هنا تشبيه بسيط لهذه المشكلة:

    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 معالجات تسمح تسلسل من المكالمات. يتم إنشاء الوعود من خلال باستخدام واجهات برمجة التطبيقات التي تعيدهم . على سبيل المثال ، تعد الوعود AJAX البديلة fetch أو jQuery's $.get بوعود.

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

مع الوعود

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

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 مولدات وهي وظائف يمكن أن تعود في المنتصف ثم تستأنف النقطة التي كانت عليها. هذا مفيد عادة للتسلسلات ، على سبيل المثال:

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

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

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

هذه الحيلة صعبة بعض الشيء ولكنها قوية للغاية تتيح لنا كتابة التعليمات البرمجية غير المتزامنة بطريقة متزامنة. هناك العديد من "المتسابقين" الذين يقومون بذلك نيابة عنك ، كتابة واحدة عبارة عن سطور قصيرة قليلة من الكود لكنها خارج نطاق هذه الإجابة. سأستخدم رمز Promise.coroutine الخاص بـ Bluebird هنا ، ولكن هناك أغلفة أخرى مثل 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
});

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

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
}

لا يزال إرجاع وعد فقط نفس :)

292
Benjamin Gruenbaum

أنت تستخدم 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);
    }
});

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

230
Nic

الحل الأبسط هو إنشاء وظيفة JavaScript واستدعائها لاستدعاء 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);    
}); 
219
Hemant Bavle

سأجيب بقصص هزلية مذهلة المظهر. الصورة الثانية هي السبب في أن resultundefined في مثال الكود.

 enter image description here

195
Johannes Fahrenkrug

Angular1

بالنسبة للأشخاص الذين يستخدمون AngularJS ، يمكنهم معالجة هذا الموقف باستخدام Promises.

هنا تقول ،

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

يمكنك العثور على شرح لطيف هنا أيضًا.

تم العثور على مثال في المستندات المذكورة أدناه.

  promiseB = promiseA.then(
    function onSuccess(result) {
      return result + 1;
    }
    ,function onError(err) {
      //Handle error
    }
  );

 // promiseB will be resolved immediately after promiseA is resolved 
 // and its value will be the result of promiseA incremented by 1.

Angular2 ولاحقا

في Angular2 مع إلقاء نظرة على المثال التالي ، ولكن مستحسن لاستخدام Observables مع Angular2.

 search(term: string) {
     return this.http
  .get(`https://api.spotify.com/v1/search?q=${term}&type=artist`)
  .map((response) => response.json())
  .toPromise();

}

يمكنك أن تستهلك هذا بهذه الطريقة ،

search() {
    this.searchService.search(this.searchField.value)
      .then((result) => {
    this.result = result.artists.items;
  })
  .catch((error) => console.error(error));
}

انظر الأصلي آخر هنا. لكن TypeScript لا يدعم es6 Promises الأصلي ، إذا كنت تريد استخدامه ، فقد تحتاج إلى مكون إضافي لذلك.

بالإضافة إلى ذلك هنا هي الوعود المواصفات تعريف هنا.

143
Maleen Abewardana

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

// 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 في نفس الموضع الذي يرتبط به الإدخال ، حتى لو كانت النتائج غير متصلة بالترتيب (بما أن مكالمات المزامنة غير المكتملة بالضرورة بالترتيب الذي بدأت به ).

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

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 يمنحك وعد ، فيمكنك استخدام Promise.all :

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

إذا كنت تعلم أن doSomethingAsync ستتجاهل الوسيطة الثانية والثالثة ، فيمكنك نقلها مباشرةً إلى map (map يستدعي رد الاتصال به باستخدام ثلاث وسيطات ، لكن معظم الأشخاص يستخدمون الأولى فقط في معظم الوقت):

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

مثال:

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(doSomethingAsync));
}
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 + (ربما باستخدام جهاز ترجمة مثل Babel ) ، يمكنك استخدام دالة async مع 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;
}
122
T.J. Crowder

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

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/

طريقة ES6 (غير متزامن - في انتظار)

(function(){
  async function getJoke(){
    let response = await fetch('http://api.icndb.com/jokes/random');
    let data = await response.json();
    return data.value;
  }

  getJoke().then((joke) => {
    console.log(joke);
  });
})();
97
Francisco Carmona

هناك طريقة أخرى لإرجاع قيمة من دالة غير متزامنة ، وهي تمرير كائن يخزن النتيجة من الدالة غير المتزامنة.

هنا مثال على ذلك:

var async = require("async");

// This wires up result back to the caller
var result = {};
var asyncTasks = [];
asyncTasks.Push(function(_callback){
    // some asynchronous operation
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;
            _callback();
        }
    });
});

async.parallel(asyncTasks, function(){
    // result is available after performing asynchronous operation
    console.log(result)
    console.log('Done');
});

أنا أستخدم الكائن result لتخزين القيمة أثناء العملية غير المتزامنة. هذا يسمح بالنتيجة تكون متاحة حتى بعد المهمة غير المتزامنة.

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

85
jsbisht

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

لذلك إذا كنت تستخدم Angular ، React أو أي أطر عمل أخرى لها طريقتان ملزمتان للبيانات ، / تم إصلاح هذه المشكلة ببساطة بالنسبة لك ، لذلك في Word السهل ، تكون النتيجة undefined في المرحلة الأولى ، لذا فقد حصلت على result = undefined قبل استلام البيانات ، ثم بمجرد حصولك على النتيجة ، سيتم تحديثها وتعيينها للقيمة الجديدة التي تستجيب لمكالمة Ajax ...

ولكن كيف يمكنك أن تفعل ذلك في نقية جافا سكريبت أو jQuery على سبيل المثال كما طلبت في هذا السؤال؟

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

على سبيل المثال ، في حالتك التي تستخدم jQuery ، يمكنك القيام بشيء مثل هذا:

$(document).ready(function(){
    function foo() {
        $.ajax({url: "api/data", success: function(data){
            fooDone(data); //after we have data, we pass it to fooDone
        }});
    };

    function fooDone(data) {
        console.log(data); //fooDone has the data and console.log it
    };

    foo(); //call happens here
});

لمزيد من المعلومات ، قم بإجراء دراسة حول الوعود و الملاحظات وهي طرق أحدث للقيام بهذه المواد غير المتزامنة.

80
Alireza

في حين تعمل الوعود وعمليات الاستعادة بشكل جيد في العديد من المواقف ، إلا أنه من الملم في الخلف التعبير عن شيء مثل:

if (!name) {
  name = async1();
}
async2(name);

سينتهي بك الأمر عبر async1 ؛ تحقق مما إذا كان name غير معرّف أم لا ، واتصل بالرد على ذلك.

async1(name, callback) {
  if (name)
    callback(name)
  else {
    doSomething(callback)
  }
}

async1(name, async2)

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

Fibers يساعد في حل المشكلة.

var Fiber = require('fibers')

function async1(container) {
  var current = Fiber.current
  var result
  doSomething(function(name) {
    result = name
    fiber.run()
  })
  Fiber.yield()
  return result
}

Fiber(function() {
  var name
  if (!name) {
    name = async1()
  }
  async2(name)
  // Make any number of async calls from here
}

يمكنك الخروج من المشروع هنا .

77
rohithpr

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

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

$.ajax({
    url: "...",
    success: callback
});
73
Pablo Matias Gomez

المثال التالي الذي كتبته يوضح كيفية

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

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

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

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

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

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

NOTEفي الآونة الأخيرة ، تتطلب واجهات برمجة التطبيقات search واجهات برمجة التطبيقات الوصول إلى رمز مميز للوصول في رؤوس الطلبات:

-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" />

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

70
loretoparisi

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

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

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

إليك نسخة صالحة من الشفرة:

(async function(){

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

})()

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

67
mikemaccana

يمكنك استخدام هذه المكتبة المخصصة (المكتوبة باستخدام وعد) لإجراء مكالمة عن بعد.

function $http(apiConfig) {
    return new Promise(function (resolve, reject) {
        var client = new XMLHttpRequest();
        client.open(apiConfig.method, apiConfig.url);
        client.send();
        client.onload = function () {
            if (this.status >= 200 && this.status < 300) {
                // Performs the function "resolve" when this.status is equal to 2xx.
                // Your logic here.
                resolve(this.response);
            }
            else {
                // Performs the function "reject" when this.status is different than 2xx.
                reject(this.statusText);
            }
        };
        client.onerror = function () {
            reject(this.statusText);
        };
    });
}

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

$http({
    method: 'get',
    url: 'google.com'
}).then(function(response) {
    console.log(response);
}, function(error) {
    console.log(error)
});
60
Vinoth Rajendran

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

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

1) حلقة الحدث

2) API الويب

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

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

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

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

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

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

 CallBack

 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://stackoverflow.com/a/14220323/7579856

55
Aniket Jha

حل آخر هو تنفيذ التعليمات البرمجية عبر المنفذ التسلسلي 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- علم (إذا كان لديه نسخة وعدت ، يمكنك تخطي هذه الخطوة):

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. قم بتشغيل الوظيفة بطريقة متزامنة عبر nsynjs:

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

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

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

54
amaksr

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

لنبدأ بوظيفة 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) ، لكنها تبدأ مؤقتًا ، والذي ينفذ دالة بعد 1 ثانية لإرجاع "wohoo". ولكن كما ترون ، فإن القيمة التي تم تعيينها للشريط هي الأشياء التي تم إرجاعها فورًا من foo () ، وليس أي شيء آخر يأتي لاحقًا.

إذن ، كيف نتعامل مع هذه المشكلة؟

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

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 القيمة (التي تنوي إرجاعها). وبالتالي ، يمكنك باختصار حل قيمة بدلاً من العودة ، في وظائف غير متزامنة.

تحديث (الوعود مع المزامنة/الانتظار)

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

ثم/الصيد الإصدار:

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

إصدار متزامن/في انتظار:

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

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

function* myGenerator() {
    const callback = yield;
    let [response] = yield $.ajax("https://stackoverflow.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 في صفيف ويتم إتلافه عند قراءته بحيث يمكن للنمط التعامل مع عمليات الاسترجاعات التي تحتوي على وسائط متعددة. على سبيل المثال مع العقدة fs :

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

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

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

مثال: تأجيل تنفيذ jQuery للعمل مع طلبات متعددة

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();
36
Mohan Dere

إجابة قصيرة : ترجع طريقة foo() فورًا ، بينما يتم تنفيذ استدعاء $ajax() بشكل غير متزامن بعد إرجاع الدالة . تكمن المشكلة في كيفية أو مكان تخزين النتائج التي يتم استردادها بواسطة استدعاء المزامنة بمجرد إرجاعها.

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

function foo(result) {
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;   // Store the async result
        }
    });
}

var result = { response: null };   // Object to hold the async result
foo(result);                       // Returns before the async completes

لاحظ أن استدعاء foo() لن يُرجع شيئًا مفيدًا. ومع ذلك ، سيتم الآن تخزين نتيجة الاستدعاء المتزامن في result.response.

33
David R Tribble

استخدم دالة callback() داخل نجاح foo(). جرب بهذه الطريقة. انها بسيطة وسهلة الفهم.

var lat = "";
var lon = "";
function callback(data) {
    lat = data.lat;
    lon = data.lon;
}
function getLoc() {
    var url = "http://ip-api.com/json"
    $.getJSON(url, function(data) {
        callback(data);
    });
}

getLoc();
33
Mahfuzur Rahman

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

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

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

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. يعمل هذا لأن $.ajax الخاصة بـ jQuery تقوم بالفعل بإرجاع نوع من الأشياء الشبيهة بالوعد. (في الممارسة العملية ، دون الخوض في التفاصيل ، نفضل أن نلف هذه الدعوة حتى نعيد وعدًا حقيقيًا ، أو نستخدم بعضًا من $.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);
}
27
user663031

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

function foo() {
    var result;

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

    return result;
}

function myCallback(response) {
    // Does something.
}
26
Khoa Bui

كان السؤال:

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

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

كيفية جعل غير متزامن رمز نظرة متزامن ؟

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

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

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

يتيح أولاً تمكين استخدام JQuery ، ليكون $ متاحًا كـ S:

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

حدد وظيفة تُرجع وعد ، في هذه الحالة ، استدعاء 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")
24
Pieter Jan Bonestroo

باستخدام 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)

})
14
Fernando Carvajal

بدلاً من إلقاء الكود عليك ، هناك مفهومان أساسيان لفهم كيفية تعامل JS مع حالات معاودة الاتصال وعدم التزامن. (هل هذه كلمة حتى؟)

حلقة الحدث ونموذج التزامن

هناك ثلاثة أشياء تحتاج إلى معرفتها ؛ قائمة الانتظار ؛ حلقة الحدث والمكدس

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

while (queue.waitForMessage()) {
   queue.processNextMessage();
}

بمجرد أن يتلقى رسالة لتشغيل شيء ما ، فإنه يضيفه إلى قائمة الانتظار. قائمة الانتظار هي قائمة بالأشياء التي تنتظر تنفيذها (مثل AJAX طلبك). تخيل ذلك مثل هذا:

 1. call foo.com/api/bar using foobarFunc
 2. Go perform an infinite loop
 ... and so on

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

function foobarFunc (var) {
  console.log(anotherFunction(var));
}

لذا فإن أي شيء يحتاجه foobarFunc إلى التنفيذ (في حالتنا anotherFunction) سيتم دفعه إلى المكدس. تم التنفيذ ، ثم نسيانه - ستنتقل حلقة الأحداث إلى الشيء التالي في قائمة الانتظار (أو تستمع للرسائل)

الشيء الرئيسي هنا هو ترتيب التنفيذ. هذا هو

عندما يتم تشغيل شيء ما

عند إجراء مكالمة باستخدام AJAX لطرف خارجي أو تشغيل أي تعليمات برمجية غير متزامنة (setTimeout على سبيل المثال) ، تعتمد Javascript على استجابة قبل أن تتمكن من المتابعة.

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

لهذا السبب مع وظيفة غير متزامن نستخدم أشياء تسمى رد الاتصال . يشبه نوعًا ما الوعد حرفيًا تمامًا. كما هو الحال في I الوعد بإرجاع شيء ما في مرحلة ما) يستخدم jQuery عمليات رد اتصال محددة تسمى deffered.donedeffered.fail و deffered.alwaysمن بين آخرين). يمكنك مشاهدتها جميعًا (هنا)

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

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

function foo(bla) {
  console.log(bla)
}

لذلك معظم الوقت (ولكن ليس دائمًا) سوف تمر foo not foo()

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

13
Matthew Brent

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

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

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

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

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

13
Haim Zamir

باستخدام الوعد

الإجابة الأكثر مثالية على هذا السؤال تستخدم Promise.

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

استعمال

ajax("GET", "/test", "acrive=1").then(function(result) {
    // Code depending on result
})
.catch(function() {
    // An error occurred
});

لكن انتظر...!

هناك مشكلة في استخدام الوعود!

لماذا يجب علينا استخدام وعدنا المخصصة؟

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

Uncaught ReferenceError: Promise is not defined

لذلك قررت تنفيذ فئة الوعد الخاصة بي لـ ES3 أدناه js مترجمين إذا لم يتم تعريفها. فقط أضف هذا الكود قبل الرمز الرئيسي الخاص بك ، ثم وعد مستخدم السلامة!

if(typeof Promise === "undefined"){
    function _classCallCheck(instance, Constructor) {
        if (!(instance instanceof Constructor)) { 
            throw new TypeError("Cannot call a class as a function"); 
        }
    }
    var Promise = function () {
        function Promise(main) {
            var _this = this;
            _classCallCheck(this, Promise);
            this.value = undefined;
            this.callbacks = [];
            var resolve = function resolve(resolveValue) {
                _this.value = resolveValue;
                _this.triggerCallbacks();
            };
            var reject = function reject(rejectValue) {
                _this.value = rejectValue;
                _this.triggerCallbacks();
            };
            main(resolve, reject);
        }
        Promise.prototype.then = function then(cb) {
            var _this2 = this;
            var next = new Promise(function (resolve) {
                _this2.callbacks.Push(function (x) {
                    return resolve(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.catch = function catch_(cb) {
            var _this2 = this;
            var next = new Promise(function (reject) {
                _this2.callbacks.Push(function (x) {
                    return reject(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.triggerCallbacks = function triggerCallbacks() {
            var _this3 = this;
            this.callbacks.forEach(function (cb) {
                cb(_this3.value);
            });
        };
        return Promise;
    }();
}
12
Amir Forsati

هنا مثال يعمل:

const validateName = async userName => {
  const url = "abc/xyz";
  try {
    const response = await axios.get(url);
    return response.data
  } catch (err) {
    return false;
  }
};

validateName("user")
 .then(data => console.log(data))
 .catch(reason => console.log(reason.message))
7
Alex Montoya