ฉันจะตอบกลับการตอบกลับจากการโทรแบบอะซิงโครนัสได้อย่างไร

Jan 09 2013

ฉันมีฟังก์ชันfooที่สร้างคำขอแบบอะซิงโครนัส ฉันจะส่งคืนการตอบกลับ / ผลลัพธ์ได้fooอย่างไร?

ฉันพยายามส่งคืนค่าจากการเรียกกลับรวมทั้งกำหนดผลลัพธ์ให้กับตัวแปรท้องถิ่นภายในฟังก์ชันและส่งคืนค่านั้น แต่ไม่มีวิธีใดที่ส่งคืนการตอบสนองอย่างแท้จริง (พวกเขาทั้งหมดจะคืนค่าundefinedหรือค่าเริ่มต้นของตัวแปรresult) .

ตัวอย่างการใช้ajaxฟังก์ชันของ jQuery :

function foo() {
    var result;

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

    return result; // It always returns `undefined`
}

ตัวอย่างการใช้ node.js:

function foo() {
    var result;

    fs.readFile("path/to/file", function(err, data) {
        result = data;
        // return data; // <- I tried that one as well
    });

    return result; // It always returns `undefined`
}

ตัวอย่างการใช้thenบล็อกของคำสัญญา:

function foo() {
    var result;

    fetch(url).then(function(response) {
        result = response;
        // return response; // <- I tried that one as well
    });

    return result; // It always returns `undefined`
}

คำตอบ

5905 FelixKling Jan 09 2013 at 00:06

→สำหรับคำอธิบายทั่วไปเพิ่มเติมเกี่ยวกับพฤติกรรม async พร้อมตัวอย่างต่างๆโปรดดู เหตุใดตัวแปรของฉันจึงไม่เปลี่ยนแปลงหลังจากที่ฉันแก้ไขภายในฟังก์ชัน - การอ้างอิงรหัสอะซิงโครนัส

→หากคุณเข้าใจปัญหาแล้วให้ข้ามไปที่แนวทางแก้ไขที่เป็นไปได้ด้านล่าง

ปัญหา

ในอาแจ็กซ์ย่อมาไม่ตรงกัน นั่นหมายถึงการส่งคำขอ (หรือรับการตอบกลับ) จะถูกนำออกจากขั้นตอนการดำเนินการตามปกติ ในตัวอย่างของคุณส่งกลับทันทีและคำสั่งถัดไปจะถูกเรียกใช้งานก่อนฟังก์ชันที่คุณส่งผ่านเมื่อเรียกกลับด้วยซ้ำ$.ajaxreturn result;success

นี่คือการเปรียบเทียบซึ่งหวังว่าจะทำให้ความแตกต่างระหว่างการไหลแบบซิงโครนัสและอะซิงโครนัสชัดเจนขึ้น:

ซิงโครนัส

ลองนึกภาพคุณโทรหาเพื่อนและขอให้เขาหาอะไรให้คุณ แม้ว่าอาจใช้เวลาสักครู่ แต่คุณก็รอโทรศัพท์และจ้องมองไปในอวกาศจนกว่าเพื่อนของคุณจะให้คำตอบที่คุณต้องการ

สิ่งเดียวกันนี้เกิดขึ้นเมื่อคุณเรียกใช้ฟังก์ชันที่มีรหัส "ปกติ":

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

var item = findItem();

// Do something with item
doSomethingElse();

แม้ว่าfindItemอาจใช้เวลาดำเนินการนาน แต่โค้ดใด ๆ ที่ตามมาvar item = findItem();ต้องรอจนกว่าฟังก์ชันจะส่งคืนผลลัพธ์

อะซิงโครนัส

คุณโทรหาเพื่อนของคุณอีกครั้งด้วยเหตุผลเดียวกัน แต่คราวนี้คุณบอกเขาว่าคุณกำลังรีบและเขาควรโทรกลับทางโทรศัพท์มือถือของคุณ คุณวางสายออกจากบ้านและทำทุกอย่างที่คุณวางแผนจะทำ เมื่อเพื่อนของคุณโทรกลับคุณกำลังจัดการกับข้อมูลที่เขาให้กับคุณ

นั่นคือสิ่งที่เกิดขึ้นเมื่อคุณร้องขอ Ajax

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

แทนที่จะรอการตอบกลับการดำเนินการจะดำเนินต่อไปทันทีและคำสั่งหลังจากเรียกใช้ Ajax แล้ว เพื่อให้ได้รับการตอบกลับในที่สุดคุณต้องจัดเตรียมฟังก์ชันที่จะถูกเรียกเมื่อได้รับการตอบกลับแล้วการโทรกลับ (สังเกตว่ามีอะไรโทรกลับไหม) คำสั่งใด ๆ ที่เกิดขึ้นหลังจากการโทรนั้นจะถูกดำเนินการก่อนที่จะมีการเรียกกลับ


โซลูชัน (s)

ยอมรับลักษณะอะซิงโครนัสของ JavaScript! แม้ว่าการดำเนินการแบบอะซิงโครนัสบางอย่างจะให้การทำงานร่วมกันแบบซิงโครนัส (เช่นเดียวกับ "Ajax") แต่โดยทั่วไปแล้วไม่แนะนำให้ใช้โดยเฉพาะอย่างยิ่งในบริบทของเบราว์เซอร์

ทำไมคุณถึงไม่ดี?

JavaScript ทำงานในเธรด UI ของเบราว์เซอร์และกระบวนการที่ทำงานเป็นเวลานานจะล็อก UI ทำให้ไม่ตอบสนอง นอกจากนี้ยังมีขีด จำกัด สูงสุดของเวลาในการดำเนินการสำหรับ JavaScript และเบราว์เซอร์จะถามผู้ใช้ว่าจะดำเนินการต่อหรือไม่

ทั้งหมดนี้เป็นประสบการณ์การใช้งานที่แย่มาก ผู้ใช้จะไม่สามารถบอกได้ว่าทุกอย่างทำงานได้ดีหรือไม่ นอกจากนี้ผลกระทบจะแย่ลงสำหรับผู้ใช้ที่มีการเชื่อมต่อช้า

ต่อไปนี้เราจะดูวิธีแก้ปัญหาที่แตกต่างกันสามวิธีที่สร้างขึ้นจากกัน:

  • สัญญากับasync/await (ES2017 + พร้อมใช้งานในเบราว์เซอร์รุ่นเก่าหากคุณใช้ทรานสไพเลอร์หรือรีเจนเนอเรเตอร์)
  • การโทรกลับ (เป็นที่นิยมในโหนด)
  • สัญญากับthen() (ES2015 + พร้อมใช้งานในเบราว์เซอร์รุ่นเก่าหากคุณใช้หนึ่งในไลบรารีคำสัญญาจำนวนมาก)

ทั้งสามมีอยู่ในเบราว์เซอร์ปัจจุบันและโหนด 7+


ES2017 +: สัญญากับ async/await

ECMAScript เวอร์ชันที่เปิดตัวในปี 2017 ได้เปิดตัวการสนับสนุนระดับไวยากรณ์สำหรับฟังก์ชันอะซิงโครนัส ด้วยความช่วยเหลือของasyncและawaitคุณสามารถเขียนแบบอะซิงโครนัสใน "สไตล์ซิงโครนัส" รหัสยังคงเป็นแบบอะซิงโครนัส แต่อ่าน / ทำความเข้าใจได้ง่ายกว่า

async/awaitสร้างขึ้นจากคำสัญญา: asyncฟังก์ชันจะส่งคืนคำสัญญาเสมอ await"แกะ" คำสัญญาและส่งผลให้ค่าที่สัญญาได้รับการแก้ไขหรือแสดงข้อผิดพลาดหากคำสัญญาถูกปฏิเสธ

สำคัญ:คุณสามารถใช้ได้awaitภายในasyncฟังก์ชันเท่านั้น ตอนนี้awaitยังไม่รองรับระดับบนสุดดังนั้นคุณอาจต้องสร้าง async IIFE ( ทันทีเรียกใช้ฟังก์ชันนิพจน์ ) เพื่อเริ่ม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;
  }
}

// Start an IIFE to use `await` at the top level
(async function(){
  let books = await getAllBooks();
  console.log(books);
})();

รองรับเบราว์เซอร์และเวอร์ชันโหนดasync/awaitปัจจุบัน คุณยังสามารถรองรับสภาพแวดล้อมที่เก่ากว่าได้โดยการเปลี่ยนรหัสของคุณเป็น ES5 ด้วยความช่วยเหลือของตัวสร้างใหม่ (หรือเครื่องมือที่ใช้ตัวสร้างใหม่เช่นBabel )


ให้ฟังก์ชันยอมรับการโทรกลับ

การเรียกกลับคือเมื่อฟังก์ชัน 1 ถูกส่งไปยังฟังก์ชัน 2 ฟังก์ชัน 2 สามารถเรียกใช้ฟังก์ชัน 1 ได้ทุกเมื่อที่พร้อม ในบริบทของกระบวนการอะซิงโครนัสการเรียกกลับจะถูกเรียกเมื่อใดก็ตามที่กระบวนการอะซิงโครนัสเสร็จสิ้น โดยปกติผลลัพธ์จะถูกส่งไปยังการเรียกกลับ

ในตัวอย่างคำถามคุณสามารถfooยอมรับการโทรกลับและใช้เป็นการsuccessโทรกลับได้ อย่างนี้

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

กลายเป็น

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

ที่นี่เรากำหนดฟังก์ชัน "อินไลน์" แต่คุณสามารถส่งผ่านการอ้างอิงฟังก์ชันใดก็ได้:

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

foo(myCallback);

foo ตัวเองถูกกำหนดไว้ดังนี้:

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

callbackจะอ้างถึงฟังก์ชั่นที่เราผ่านไปเมื่อเราเรียกมันและเราผ่านมันไปfoo successกล่าวคือเมื่อคำขอ Ajax สำเร็จ$.ajaxจะเรียกcallbackและส่งการตอบกลับไปยังการเรียกกลับ (ซึ่งสามารถอ้างถึงได้resultเนื่องจากนี่คือวิธีที่เรากำหนดการเรียกกลับ)

คุณยังสามารถประมวลผลการตอบกลับก่อนที่จะส่งไปยังการโทรกลับ:

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

การเขียนโค้ดโดยใช้การเรียกกลับง่ายกว่าที่คิด ท้ายที่สุด JavaScript ในเบราว์เซอร์เป็นเหตุการณ์ที่ขับเคลื่อนด้วยเหตุการณ์ (เหตุการณ์ DOM) เป็นอย่างมาก การได้รับการตอบสนอง Ajax ไม่ใช่สิ่งอื่นใดนอกจากเหตุการณ์
ความยากลำบากอาจเกิดขึ้นเมื่อคุณต้องทำงานกับรหัสของบุคคลที่สาม แต่ปัญหาส่วนใหญ่สามารถแก้ไขได้เพียงแค่คิดผ่านขั้นตอนของแอปพลิเคชัน


ES2015 +: สัญญากับตอนนั้น ()

สัญญา APIเป็นคุณลักษณะใหม่ของ ECMAScript 6 (ES2015) แต่มันก็มีดีการสนับสนุนเบราว์เซอร์แล้ว นอกจากนี้ยังมีไลบรารีจำนวนมากที่ใช้ Promises API มาตรฐานและมีวิธีการเพิ่มเติมเพื่อลดความสะดวกในการใช้งานและองค์ประกอบของฟังก์ชันแบบอะซิงโครนัส (เช่น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).
  });

นำไปใช้กับการโทร Ajax ของเราเราสามารถใช้คำสัญญาเช่นนี้:

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 - สัญญา JavaScript

หมายเหตุด้านข้าง: วัตถุที่รอการตัดบัญชีของ jQuery

ออบเจ็กต์รอการตัดบัญชีเป็นการใช้งานตามสัญญาที่กำหนดเองของ jQuery (ก่อนที่ Promise API จะเป็นมาตรฐาน) พวกเขาทำตัวเหมือนคำสัญญา แต่เปิดเผย API ที่แตกต่างกันเล็กน้อย

ทุกเมธอด 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 Deferred ทันทีไม่ใช่การตอบสนองจากเซิร์ฟเวอร์ นั่นหมายความว่า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

หากคุณใช้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 โดยธรรมชาติแล้วมักจะไม่ตรงกันเสมอ (อีกเหตุผลหนึ่งที่ไม่ต้องพิจารณาตัวเลือกนี้ด้วยซ้ำ)

1096 BenjaminGruenbaum May 30 2013 at 06:30

หากคุณไม่ได้ใช้ 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 ฉันตัดสินใจที่จะให้ทางเลือกสำหรับผู้ที่ไม่ได้เป็น

( หมายเหตุสำหรับผู้ที่ใช้fetchAPI ใหม่Angular หรือสัญญาฉันได้เพิ่มคำตอบด้านล่าง )


สิ่งที่คุณกำลังเผชิญ

นี่คือสรุปสั้น ๆ ของ "คำอธิบายของปัญหา" จากคำตอบอื่น ๆ หากคุณไม่แน่ใจหลังจากอ่านสิ่งนี้แล้วให้อ่านสิ่งนั้น

ใน AJAX ย่อมาไม่ตรงกัน นั่นหมายถึงการส่งคำขอ (หรือรับการตอบกลับ) จะถูกนำออกจากขั้นตอนการดำเนินการตามปกติ ในตัวอย่างของคุณส่งกลับทันทีและคำสั่งถัดไปจะถูกเรียกใช้งานก่อนฟังก์ชันที่คุณส่งผ่านเมื่อเรียกกลับด้วยซ้ำ.sendreturn 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. Synchronous 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

ตอนนี้เรามากำหนด foo ตัวเองเพื่อดำเนินการตามนั้น

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

411 cocco Aug 19 2013 at 15:06

XMLHttpRequest 2 (ก่อนอื่นอ่านคำตอบจาก Benjamin 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 นี้ (สามวิธีโดยใช้ชื่อตัวแปร XMLHttpRequest):

ง่ายที่สุด:

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.statusTextMethod not Allowed

ในกรณีที่สองมันใช้งานได้ง่าย คุณต้องตรวจสอบที่ฝั่งเซิร์ฟเวอร์ว่าคุณส่งข้อมูลโพสต์ที่ถูกต้องหรือไม่

ไม่อนุญาตให้ข้ามโดเมนเกิดข้อผิดพลาดโดยอัตโนมัติ

ในการตอบสนองข้อผิดพลาดไม่มีรหัสข้อผิดพลาด

มีเพียงสิ่งthis.typeที่ถูกตั้งค่าเป็นข้อผิดพลาด

ทำไมต้องเพิ่มตัวจัดการข้อผิดพลาดหากคุณไม่สามารถควบคุมข้อผิดพลาดได้โดยสิ้นเชิง? displayAjax()ส่วนใหญ่ของข้อผิดพลาดจะถูกส่งกลับภายในนี้ในฟังก์ชันการเรียกกลับ

ดังนั้น: ไม่จำเป็นต้องตรวจสอบข้อผิดพลาดว่าคุณสามารถคัดลอกและวาง URL ได้อย่างถูกต้องหรือไม่ ;)

PS: ในการทดสอบครั้งแรกที่ฉันเขียน x ('x', displayAjax) ... และมันก็ได้รับการตอบสนองโดยสิ้นเชิง ... ??? ดังนั้นฉันจึงตรวจสอบโฟลเดอร์ที่เป็นที่ตั้งของ HTML และมีไฟล์ชื่อ 'x.xml' ดังนั้นแม้ว่าคุณจะลืมนามสกุลของไฟล์ของคุณ XMLHttpRequest 2 จะพบว่า ฉันฮ่า ๆ


อ่านไฟล์แบบซิงโครนัส

อย่าทำอย่างนั้น

หากคุณต้องการบล็อกเบราว์เซอร์สักครู่ให้โหลด.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 loop ... แต่จริงจังเหรอ?)

อีกประเด็นหนึ่งคือ ... ถ้าคุณทำงานกับ API หรือแค่ไฟล์ในรายการของคุณเองหรืออะไรก็ตามที่คุณมักจะใช้ฟังก์ชั่นที่แตกต่างกันสำหรับแต่ละคำขอ ...

เฉพาะในกรณีที่คุณมีหน้าที่โหลด XML / JSON เดียวกันหรืออะไรก็ตามที่คุณต้องการเพียงฟังก์ชันเดียว ในกรณีนั้นให้แก้ไขฟังก์ชัน Ajax เล็กน้อยและแทนที่ b ด้วยฟังก์ชันพิเศษของคุณ


ฟังก์ชั่นด้านบนมีไว้สำหรับการใช้งานพื้นฐาน

หากคุณต้องการขยายฟังก์ชัน ...

ใช่คุณสามารถ.

ฉันใช้ API จำนวนมากและหนึ่งในฟังก์ชันแรกที่ฉันรวมเข้ากับทุกหน้า HTML เป็นฟังก์ชัน Ajax แรกในคำตอบนี้ด้วย GET เท่านั้น ...

แต่คุณสามารถทำสิ่งต่างๆมากมายด้วย XMLHttpRequest 2:

ฉันสร้างตัวจัดการการดาวน์โหลด (ใช้ช่วงทั้งสองด้านพร้อมประวัติย่อโปรแกรมอ่านไฟล์ระบบไฟล์) ตัวแปลงตัวปรับขนาดรูปภาพต่างๆโดยใช้ผ้าใบเติมข้อมูลฐานข้อมูล SQL ของเว็บด้วย base64images และอื่น ๆ อีกมากมาย ... แต่ในกรณีเหล่านี้คุณควรสร้างฟังก์ชันเฉพาะสำหรับสิ่งนั้น จุดประสงค์ ... บางครั้งคุณต้องการหยดบัฟเฟอร์อาร์เรย์คุณสามารถตั้งค่าส่วนหัวแทนที่ mimetype และมีอื่น ๆ อีกมากมาย ...

แต่คำถามนี่คือวิธีส่งคืนการตอบกลับ Ajax ... (ฉันเพิ่มวิธีง่ายๆ)

326 BenjaminGruenbaum May 12 2015 at 09:22

หากคุณกำลังใช้คำสัญญาคำตอบนี้เหมาะสำหรับคุณ

ซึ่งหมายความว่า 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ตัวจัดการ

ซึ่งหมายความว่าเมื่อคุณกลับมาจัดการคุณได้กำหนดไว้ไม่ได้ดำเนินการเลย ในทางกลับกันหมายความว่าค่าที่คุณส่งกลับมาไม่ได้ถูกตั้งค่าเป็นค่าที่ถูกต้องทันเวลาdatathen

นี่คือการเปรียบเทียบง่ายๆสำหรับปัญหา:

    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ตัวจัดการอนุญาตให้มีการเชื่อมโยงการโทร สัญญาถูกสร้างขึ้นโดยใช้ API ที่พวกเขากลับ ตัวอย่างเช่นการเปลี่ยน AJAX ที่ทันสมัยกว่าfetchหรือ$.getสัญญาการคืนสินค้าของ jQuery

เมื่อเราเรียกร้อง.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 แนะนำเครื่องกำเนิดไฟฟ้าซึ่งเป็นฟังก์ชันที่สามารถกลับมาอยู่ตรงกลางแล้วกลับสู่จุดเดิม โดยทั่วไปจะมีประโยชน์สำหรับลำดับตัวอย่างเช่น:

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ที่นี่ แต่มีห่ออื่น ๆ เช่นหรือcoQ.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
}

มันยังคงคืนคำสัญญาเหมือนเดิม :)

256 Nic May 23 2014 at 09:05

คุณใช้ 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);
    }
});

การส่งคืนสิ่งใด ๆ ในตัวจัดการการส่งจะไม่ทำอะไรเลย คุณต้องส่งต่อข้อมูลหรือทำสิ่งที่คุณต้องการโดยตรงภายในฟังก์ชันความสำเร็จ

242 HemantBavle Feb 19 2014 at 01:58

วิธีแก้ปัญหาที่ง่ายที่สุดคือสร้างฟังก์ชัน JavaScript และเรียกมันว่า Ajax successcallback

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);    
}); 
229 JohannesFahrenkrug Aug 11 2016 at 21:17

ฉันจะตอบด้วยการ์ตูนวาดด้วยมือที่ดูน่ากลัว ภาพที่สองคือสาเหตุที่resultอยู่undefinedในตัวอย่างโค้ดของคุณ

165 MaleenAbewardana Aug 26 2014 at 15:11

เชิงมุม 1

สำหรับผู้ที่ใช้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ที่มีลักษณะตัวอย่างต่อไปนี้ แต่ขอแนะนำให้ใช้กับObservablesAngular2

 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 ดั้งเดิมหากคุณต้องการใช้คุณอาจต้องใช้ปลั๊กอินสำหรับสิ่งนั้น

นอกจากนี้ที่นี่เป็นสัญญาที่สเปคกำหนดที่นี่

159 T.J.Crowder May 03 2017 at 23:59

คำตอบส่วนใหญ่ที่นี่ให้คำแนะนำที่เป็นประโยชน์เมื่อคุณมีการดำเนินการ async เดียว แต่บางครั้งสิ่งนี้จะเกิดขึ้นเมื่อคุณต้องการดำเนินการแบบอะซิงโครนัสสำหรับแต่ละรายการในอาร์เรย์หรือโครงสร้างอื่น ๆ ที่คล้ายรายการ สิ่งล่อใจคือการทำสิ่งนี้:

// 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ยังไม่ทำงานตามเวลาที่คุณพยายามใช้ผลลัพธ์

ดังนั้นหากคุณมีอาร์เรย์ (หรือรายการบางประเภท) และต้องการดำเนินการแบบ async สำหรับแต่ละรายการคุณมีสองตัวเลือก: ดำเนินการแบบขนาน (ทับซ้อนกัน) หรือเป็นชุด (ทีละรายการตามลำดับ)

ขนาน

คุณสามารถเริ่มต้นทั้งหมดและติดตามจำนวนการโทรกลับที่คุณคาดหวังจากนั้นใช้ผลลัพธ์เมื่อคุณได้รับการติดต่อกลับจำนวนมากดังกล่าว:

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เปลี่ยนแปลงไปในขณะที่การโทรยังคงค้างอยู่ ... )

สังเกตว่าเราใช้indexfrom forEachเพื่อบันทึกผลลัพธ์ในresultsตำแหน่งเดียวกับรายการที่เกี่ยวข้องแม้ว่าผลลัพธ์จะออกมาไม่เป็นระเบียบก็ตาม (เนื่องจากการเรียก async ไม่จำเป็นต้องเสร็จสมบูรณ์ตามลำดับที่เริ่มต้น)

แต่ถ้าคุณต้องการส่งคืนผลลัพธ์เหล่านั้นจากฟังก์ชันล่ะ? ดังที่คำตอบอื่น ๆ ได้ชี้ให้เห็นว่าคุณทำไม่ได้ คุณต้องให้ฟังก์ชันของคุณยอมรับและโทรกลับ (หรือคืนสัญญา ) นี่คือเวอร์ชันโทรกลับ:

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;
}

หรือนี่คือเวอร์ชันที่ส่งคืน a 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 + arrow :

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;
}

113 FranciscoCarmona Jun 02 2016 at 15:31

ดูตัวอย่างนี้:

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 (async - รอ)

(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);
  });
})();
112 Alireza May 24 2017 at 16:38

นี่เป็นหนึ่งในสถานที่ที่มีสองวิธีในการผูกข้อมูลหรือจัดเก็บแนวคิดที่ใช้ในเฟรมเวิร์ก JavaScript ใหม่จำนวนมากจะทำงานได้ดีสำหรับคุณ ...

ดังนั้นหากคุณใช้Angular, Reactหรือเฟรมเวิร์กอื่น ๆ ที่ทำสองวิธีในการผูกข้อมูลหรือจัดเก็บแนวคิดปัญหานี้ได้รับการแก้ไขสำหรับคุณดังนั้นในคำง่ายๆผลลัพธ์ของคุณจึงundefinedอยู่ในขั้นตอนแรกดังนั้นคุณจะได้รับresult = undefinedก่อนที่คุณจะได้รับ ข้อมูลจากนั้นทันทีที่คุณได้รับผลลัพธ์จะได้รับการอัปเดตและได้รับการกำหนดค่าใหม่ซึ่งการตอบสนองของการโทร Ajax ของคุณ ...

แต่คุณสามารถทำได้อย่างไรในjavascriptหรือjQuery ที่บริสุทธิ์เช่นที่คุณถามในคำถามนี้?

คุณสามารถใช้โทรกลับ , สัญญาและเมื่อเร็ว ๆ นี้ที่สังเกตจะจัดการกับมันสำหรับคุณเช่นในสัญญาที่เรามีฟังก์ชั่นบางอย่างเช่นsuccess()หรือthen()ซึ่งจะต้องถูกประหารชีวิตเมื่อข้อมูลของคุณพร้อมสำหรับคุณเช่นเดียวกันกับการเรียกกลับหรือสมัครฟังก์ชั่นในการสังเกต

ตัวอย่างเช่นในกรณีของคุณที่คุณใช้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
});

สำหรับการศึกษาข้อมูลเพิ่มเติมเกี่ยวกับคำสัญญาและสิ่งที่สังเกตได้ซึ่งเป็นวิธีใหม่กว่าในการทำ async stuffs นี้

105 AnishK. Nov 01 2017 at 03:12

เป็นปัญหาที่เราพบบ่อยมากในขณะที่ต้องดิ้นรนกับ 'ความลึกลับ' ของ JavaScript ให้ฉันลองทำความเข้าใจความลึกลับนี้ในวันนี้

เริ่มต้นด้วยฟังก์ชัน 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 () undefinedซึ่งเป็นอะไรเช่นเพียง

แล้วเราจะจัดการกับปัญหานี้อย่างไร?

ลองถามฟังก์ชั่นของเราสำหรับ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ค่า (ซึ่งคุณต้องการส่งคืน) ดังนั้นในระยะสั้นคุณแก้ไขค่าแทนที่จะส่งคืนในฟังก์ชันอะซิงโครนัส

UPDATE (สัญญากับ async / await)

นอกเหนือจากการใช้then/catchเพื่อทำงานตามคำสัญญาแล้วยังมีอีกหนึ่งแนวทาง แนวคิดคือการรับรู้ฟังก์ชันอะซิงโครนัสจากนั้นรอให้คำสัญญาแก้ไขก่อนที่จะย้ายไปยังบรรทัดถัดไปของโค้ด มันยังคงเป็นเพียงส่วนpromisesใต้ฝากระโปรง แต่มีวิธีการสังเคราะห์ที่แตกต่างออกไป เพื่อให้สิ่งต่างๆชัดเจนขึ้นคุณสามารถค้นหาการเปรียบเทียบด้านล่าง:

จากนั้น / จับเวอร์ชัน:

function saveUsers(){
     getUsers()
      .then(users => {
         saveSomewhere(users);
      })
      .catch(err => {
          console.error(err);
       })
 }

async / await เวอร์ชัน:

  async function saveUsers(){
     try{
        let users = await getUsers()
        saveSomewhere(users);
     }
     catch(err){
        console.error(err);
     }
  }
101 jsbisht Sep 02 2015 at 19:54

อีกวิธีหนึ่งในการคืนค่าจากฟังก์ชันอะซิงโครนัสคือการส่งผ่านวัตถุที่จะเก็บผลลัพธ์จากฟังก์ชันอะซิงโครนัส

นี่คือตัวอย่างของสิ่งเดียวกัน:

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วัตถุเพื่อเก็บค่าระหว่างการดำเนินการแบบอะซิงโครนัส สิ่งนี้ช่วยให้ผลลัพธ์พร้อมใช้งานแม้หลังจากงานอะซิงโครนัส

ฉันใช้แนวทางนี้มาก ฉันสนใจที่จะทราบว่าแนวทางนี้ทำงานได้ดีเพียงใดเมื่อมีการเชื่อมโยงผลลัพธ์กลับผ่านโมดูลที่ต่อเนื่องกัน

89 rohithpr Jan 26 2016 at 00:43

แม้ว่าคำสัญญาและการโทรกลับจะทำงานได้ดีในหลาย ๆ สถานการณ์ แต่ก็เป็นความเจ็บปวดที่จะต้องแสดงความรู้สึกเช่น:

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
}

คุณสามารถเช็คเอาโครงการที่นี่

88 loretoparisi Apr 13 2016 at 05:55

ตัวอย่างต่อไปนี้ที่ฉันเขียนแสดงวิธีการ

  • จัดการการโทร HTTP แบบอะซิงโครนัส
  • รอการตอบกลับจากการเรียก API แต่ละครั้ง
  • ใช้รูปแบบสัญญา
  • ใช้รูปแบบPromise.allเพื่อเข้าร่วมการโทร HTTP หลายสาย

ตัวอย่างการทำงานนี้มีอยู่ในตัว มันจะกำหนดออบเจ็กต์คำของ่ายๆที่ใช้XMLHttpRequestอ็อบเจกต์หน้าต่างเพื่อโทรออก มันจะกำหนดฟังก์ชั่นง่ายๆเพื่อรอให้คำสัญญามากมายเสร็จสมบูรณ์

บริบท. ตัวอย่างกำลังค้นหาจุดสิ้นสุดของSpotify Web APIเพื่อค้นหาplaylistวัตถุสำหรับชุดสตริงการสืบค้นที่กำหนด:

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

สำหรับแต่ละรายการ Promise ใหม่จะเริ่มบล็อก - ExecutionBlockแยกวิเคราะห์ผลลัพธ์กำหนดเวลาชุดของสัญญาใหม่ตามอาร์เรย์ผลลัพธ์ซึ่งเป็นรายการของuserวัตถุSpotify และดำเนินการเรียก HTTP ใหม่ภายในExecutionProfileBlockแบบอะซิงโครนัส

แล้วคุณจะเห็นโครงสร้างสัญญาซ้อนกันที่ช่วยให้คุณหลายวางไข่และไม่ตรงกันสมบูรณ์ซ้อนกันโทร HTTP Promise.allและเข้าร่วมผลที่ได้จากส่วนย่อยของแต่ละสายผ่าน

หมายเหตุ Spotify searchAPI ล่าสุดจะต้องมีการระบุโทเค็นการเข้าถึงในส่วนหัวของคำขอ:

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

ผมได้กล่าวถึงอย่างกว้างขวางการแก้ปัญหานี้ที่นี่

84 PabloMatiasGomez Apr 22 2016 at 21:47

คำตอบสั้น ๆ คือคุณต้องใช้การโทรกลับเช่นนี้:

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

$.ajax({
    url: "...",
    success: callback
});
82 mikemaccana Jun 02 2017 at 16:51

2017 คำตอบ: ตอนนี้คุณสามารถทำสิ่งที่คุณต้องการได้ในทุกเบราว์เซอร์และโหนดปัจจุบัน

มันค่อนข้างง่าย:

  • คืนคำสัญญา
  • ใช้'await'ซึ่งจะบอกให้ JavaScript รอคำสัญญาที่จะแก้ไขเป็นค่า (เช่นการตอบสนอง HTTP)
  • เพิ่มคีย์เวิร์ด'async'ในฟังก์ชันพาเรนต์

นี่คือโค้ดเวอร์ชันที่ใช้งานได้:

(async function(){

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

})()

await ได้รับการสนับสนุนในเบราว์เซอร์ปัจจุบันและโหนด 8 ทั้งหมด

80 AniketJha Feb 03 2018 at 13:06

Js เป็นเธรดเดียว

เบราว์เซอร์สามารถแบ่งออกเป็นสามส่วน:

1) ห่วงเหตุการณ์

2) Web API

3) คิวงาน

Event Loop จะทำงานตลอดไปเช่นชนิดของการวนซ้ำที่ไม่มีที่สิ้นสุด Event Queue คือการที่ฟังก์ชันทั้งหมดของคุณถูกผลักดันในบางเหตุการณ์ (ตัวอย่าง: คลิก) นี่คือการดำเนินการทีละคิวและใส่ลงใน Event loop ซึ่งเรียกใช้ฟังก์ชันนี้และเตรียมมันเอง สำหรับฟังก์ชันถัดไปหลังจากรันอันแรกซึ่งหมายความว่าการดำเนินการของฟังก์ชันหนึ่งจะไม่เริ่มทำงานจนกว่าฟังก์ชันก่อนที่จะดำเนินการในคิวในลูปเหตุการณ์

ตอนนี้ให้เราคิดว่าเราผลักสองฟังก์ชั่นในคิวหนึ่งคือการรับข้อมูลจากเซิร์ฟเวอร์และอีกอันใช้ข้อมูลนั้นเราพุชฟังก์ชัน serverRequest () ในคิวก่อนจากนั้นจึงใช้ฟังก์ชัน utiliseData () ฟังก์ชั่น serverRequest ไปในลูปเหตุการณ์และโทรไปยังเซิร์ฟเวอร์โดยที่เราไม่ทราบว่าจะต้องใช้เวลาเท่าไรในการรับข้อมูลจากเซิร์ฟเวอร์ดังนั้นกระบวนการนี้จึงคาดว่าจะใช้เวลาและเราจึงยุ่งกับการวนซ้ำเหตุการณ์ของเราดังนั้นจึงทำให้หน้าของเราค้างซึ่งเป็นที่ที่เว็บ API เข้ามามีบทบาทโดยใช้ฟังก์ชันนี้จากลูปเหตุการณ์และเกี่ยวข้องกับเซิร์ฟเวอร์ที่ทำให้การวนซ้ำของเหตุการณ์เป็นอิสระเพื่อให้เราสามารถเรียกใช้ฟังก์ชันถัดไปจากคิวได้ฟังก์ชันถัดไปในคิวคือ utiliseData () ซึ่งจะวนซ้ำ แต่เนื่องจากไม่มีข้อมูลจึงไป การเสียและการดำเนินการของฟังก์ชั่นถัดไปจะดำเนินต่อไปจนจบคิว (สิ่งนี้เรียกว่าการเรียก Async เช่นเราสามารถทำอย่างอื่นได้จนกว่าเราจะได้รับข้อมูล)

สมมติว่าฟังก์ชัน serverRequest () ของเรามีคำสั่งส่งคืนในรหัสเมื่อเราได้รับข้อมูลกลับจากเซิร์ฟเวอร์ Web API จะผลักดันให้อยู่ในคิวที่ท้ายคิว เนื่องจากถูกผลักไปที่ท้ายคิวเราจึงไม่สามารถใช้ข้อมูลของมันได้เนื่องจากไม่มีฟังก์ชันเหลืออยู่ในคิวของเราที่จะใช้ข้อมูลนี้ ดังนั้นจึงไม่สามารถคืนบางสิ่งจาก Async Call ได้

ดังนั้นวิธีแก้ปัญหานี้คือการเรียกกลับหรือสัญญา

ภาพจากหนึ่งในคำตอบที่นี่อธิบายการใช้การโทรกลับอย่างถูกต้อง ...เราให้ฟังก์ชันของเรา (ฟังก์ชันที่ใช้ข้อมูลที่ส่งคืนจากเซิร์ฟเวอร์) ไปยังเซิร์ฟเวอร์การเรียกใช้ฟังก์ชัน

 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");
}

การโทรกลับ Javscript.info

70 VinothRajendran May 26 2016 at 20:26

คุณสามารถใช้ไลบรารีที่กำหนดเองนี้ (เขียนโดยใช้ Promise) เพื่อโทรทางไกล

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)
});
70 amaksr May 27 2017 at 09:47

ทางออกก็คือการดำเนินการรหัสผ่านผู้บริหารลำดับ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-awareness wrapper (หากมีเวอร์ชันที่สัญญาไว้คุณสามารถข้ามขั้นตอนนี้ได้):

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

42 James Feb 17 2018 at 22:26

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 หรือ closed-compiler เพื่อสร้าง ECMAScript 5

การเรียกกลับ...argsจะถูกรวมไว้ในอาร์เรย์และถูกทำลายเมื่อคุณอ่านเพื่อให้รูปแบบสามารถรับมือกับการเรียกกลับที่มีหลายอาร์กิวเมนต์ได้ ตัวอย่างเช่นกับโหนด fs :

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
39 MohanDere Aug 13 2016 at 16:36

ต่อไปนี้เป็นแนวทางบางประการในการทำงานกับคำขอแบบอะซิงโครนัส:

  1. วัตถุสัญญาของเบราว์เซอร์
  2. ถาม - ไลบรารีสัญญาสำหรับ JavaScript
  3. A + สัญญา js
  4. jQuery รอการตัดบัญชี
  5. XMLHttpRequest API
  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();

38 4revs,3users92%user663031 Jan 23 2016 at 10:28

เราพบว่าตัวเองอยู่ในจักรวาลซึ่งดูเหมือนจะก้าวหน้าไปตามมิติที่เราเรียกว่า "เวลา" เราไม่เข้าใจจริงๆว่าเวลาคืออะไร แต่เราได้พัฒนานามธรรมและคำศัพท์ที่ช่วยให้เราสามารถหาเหตุผลและพูดถึงเรื่องนี้: "อดีต", "ปัจจุบัน", "อนาคต", "ก่อน", "หลัง"

ระบบคอมพิวเตอร์ที่เราสร้างขึ้น - มากขึ้นเรื่อย ๆ - มีเวลาเป็นมิติที่สำคัญ บางสิ่งถูกกำหนดให้เกิดขึ้นในอนาคต จากนั้นสิ่งอื่น ๆ จะต้องเกิดขึ้นหลังจากที่สิ่งแรกเหล่านั้นเกิดขึ้นในที่สุด นี่คือแนวคิดพื้นฐานที่เรียกว่า "asynchronicity" ในโลกที่มีเครือข่ายมากขึ้นเรื่อย ๆ กรณีที่พบบ่อยที่สุดของ asynchronicity กำลังรอให้ระบบระยะไกลตอบสนองคำขอบางอย่าง

ลองพิจารณาตัวอย่าง คุณโทรหาคนส่งนมและสั่งนม เมื่อได้มาแล้วคุณต้องการใส่ลงในกาแฟของคุณ คุณไม่สามารถใส่นมลงในกาแฟได้ในขณะนี้เนื่องจากยังไม่ได้ใส่นม คุณต้องรอให้มันมาก่อนที่จะใส่ลงในกาแฟของคุณ กล่าวอีกนัยหนึ่งสิ่งต่อไปนี้จะใช้ไม่ได้:

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_milkkicks 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คำหลักที่ใช้ภายในฟังก์ชันชนิดพิเศษที่เรียกว่าฟังก์ชัน "async" คุณลักษณะนี้เป็นส่วนหนึ่งของ 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);
}
37 DavidRTribble Sep 24 2015 at 05:52

คำตอบสั้น ๆ : foo()วิธีการของคุณจะส่งกลับทันทีในขณะที่การ$ajax()โทรดำเนินการแบบอะซิงโครนัสหลังจากที่ฟังก์ชันกลับมา ปัญหาคือจะจัดเก็บผลลัพธ์ที่เรียกโดย async call ได้อย่างไรเมื่อส่งกลับ

มีวิธีแก้ปัญหาหลายอย่างในชุดข้อความนี้ บางทีวิธีที่ง่ายที่สุดคือส่งอ็อบเจกต์ไปยังfoo()เมธอดและเก็บผลลัพธ์ไว้ในสมาชิกของอ็อบเจ็กต์นั้นหลังจากการเรียก async เสร็จสิ้น

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()ยังจะไม่ส่งคืนสิ่งที่เป็นประโยชน์ อย่างไรก็ตามผลลัพธ์ของการโทรแบบ async จะถูกเก็บไว้ในresult.responseไฟล์.

36 MahfuzurRahman Apr 24 2017 at 15:09

ใช้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();
30 AmirFo Dec 07 2018 at 21:10

การใช้คำสัญญา

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

ดังนั้นฉันจึงตัดสินใจใช้คลาส Promise ของตัวเองสำหรับES3 ให้ต่ำกว่าคอมไพเลอร์ js หากไม่ได้กำหนดไว้ เพียงเพิ่มรหัสนี้ก่อนรหัสหลักของคุณจากนั้นใช้ Promise อย่างปลอดภัย!

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;
    }();
}
29 PieterJanBonestroo Jan 14 2018 at 02:13

คำถามคือ:

ฉันจะตอบกลับการตอบกลับจากการโทรแบบอะซิงโครนัสได้อย่างไร

ซึ่งสามารถตีความได้ว่า:

จะทำให้โค้ดอะซิงโครนัสดูซิงโครนัสได้อย่างไร?

การแก้ปัญหาจะได้รับการหลีกเลี่ยงการเรียกกลับและใช้การรวมกันของสัญญาและasync / รอคอย

ผมขอยกตัวอย่างคำขอ Ajax

(แม้ว่าจะสามารถเขียนด้วย Javascript ได้ แต่ฉันก็ชอบที่จะเขียนใน Python และรวบรวมเป็น 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")
28 KhoaBui Jul 06 2017 at 03:28

แน่นอนว่ามีหลายวิธีเช่นคำขอแบบซิงโครนัสสัญญา แต่จากประสบการณ์ของฉันฉันคิดว่าคุณควรใช้วิธีการโทรกลับ เป็นเรื่องปกติที่พฤติกรรมแบบอะซิงโครนัสของ Javascript ดังนั้นข้อมูลโค้ดของคุณสามารถเขียนใหม่ได้แตกต่างกันเล็กน้อย:

function foo() {
    var result;

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

    return result;
}

function myCallback(response) {
    // Does something.
}
26 SanjiMika Jan 20 2020 at 05:23

หลังจากอ่านคำตอบทั้งหมดที่นี่และจากประสบการณ์ของฉันฉันต้องการที่จะดำเนินการต่อรายละเอียดของcallback, promise and async/awaitการเขียนโปรแกรมแบบอะซิงโครนัสใน JavaScript

1) การโทรกลับ:เหตุผลพื้นฐานสำหรับการโทรกลับคือการเรียกใช้โค้ดเพื่อตอบสนองต่อเหตุการณ์ (ดูตัวอย่างด้านล่าง) เราใช้การโทรกลับใน JavaScript ทุกครั้ง

const body = document.getElementsByTagName('body')[0];
function callback() {
  console.log('Hello');
}
body.addEventListener('click', callback);

แต่ถ้าคุณต้องใช้การเรียกกลับที่ซ้อนกันหลายรายการในตัวอย่างด้านล่างมันจะแย่มากสำหรับการปรับโครงสร้างโค้ด

asyncCallOne(function callback1() {
  asyncCallTwo(function callback2() {
    asyncCallThree(function callback3() {
        ...
    })
  })
})

2) Promise:ไวยากรณ์ ES6 - สัญญาแก้ปัญหาการเรียกกลับนรก!

const myFirstPromise = new Promise((resolve, reject) => {
  // We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
  // In this example, we use setTimeout(...) to simulate async code. 
  // In reality, you will probably be using something like XHR request or an HTML5 API.
  setTimeout(() => {
    resolve("Success!")  // Yay! Everything went well!
  }, 250)
}) 

myFirstPromise
  .then((res) => {
    return res.json();
  })
  .then((data) => {
    console.log(data);
  })
  .catch((e) => {
    console.log(e);
  });

myFirstPromise เป็นอินสแตนซ์ Promise ที่แสดงถึงกระบวนการของรหัส async ฟังก์ชันแก้ไขจะส่งสัญญาณว่าอินสแตนซ์ Promise เสร็จสิ้นแล้ว หลังจากนั้นเราสามารถเรียก. then () (chain of. แล้วตามที่คุณต้องการ) และ .catch () บนอินสแตนซ์คำสัญญา:

then — Runs a callback you pass to it when the promise has fulfilled.
catch — Runs a callback you pass to it when something went wrong.

3) Async / Await:ไวยากรณ์ใหม่ ES6 - Await เป็นไวยากรณ์น้ำตาลของ Promise!

ฟังก์ชัน Async ช่วยให้เรามีไวยากรณ์ที่ชัดเจนและกระชับซึ่งช่วยให้เราเขียนโค้ดน้อยลงเพื่อให้ได้ผลลัพธ์แบบเดียวกับที่เราจะได้รับตามคำสัญญา Async / Await มีลักษณะคล้ายกับรหัสซิงโครนัสและรหัสซิงโครนัสนั้นอ่านและเขียนได้ง่ายกว่ามาก ที่จะจับข้อผิดพลาดกับ Async / try...catchรอเราสามารถใช้บล็อก ที่นี่คุณไม่จำเป็นต้องเขียนโซ่ของ. then () ของไวยากรณ์ Promise

const getExchangeRate = async () => {
  try {
    const res = await fetch('https://getExchangeRateData');
    const data = await res.json();
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

getExchangeRate();

สรุป: นี่คือไวยากรณ์ทั้งสามสำหรับการเขียนโปรแกรมแบบอะซิงโครนัสใน JavaScript ที่คุณเข้าใจดี ดังนั้นถ้าเป็นไปได้ขอแนะนำว่าคุณควรใช้ "สัญญา" หรือ "async / await" ในการปรับโครงสร้างรหัสอะซิงโครนัสของคุณใหม่ (ส่วนใหญ่เป็นคำขอ XHR) !

20 MatthewBrent May 04 2018 at 22:56

แทนที่จะโยนโค้ดใส่คุณมี 2 แนวคิดที่เป็นกุญแจสำคัญในการทำความเข้าใจว่า 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 จึงดำเนินการรายการถัดไปในคิวในขณะที่รอให้ข้อความถูกเพิ่มกลับไปที่คิว

นั่นเป็นเหตุผลที่มีการทำงานไม่ตรงกันเราจะใช้สิ่งที่เรียกว่าการเรียกกลับ มันเหมือนกับคำสัญญาทีเดียว ในขณะที่ฉันสัญญาว่าจะคืนบางสิ่งบางอย่างในบางจุด jQuery ใช้การเรียกกลับเฉพาะที่เรียกว่าdeffered.done deffered.failและdeffered.always(ท่ามกลางคนอื่น ๆ ) คุณสามารถดูทั้งหมดได้ที่นี่

ดังนั้นสิ่งที่คุณต้องทำคือส่งผ่านฟังก์ชันที่สัญญาว่าจะดำเนินการในบางจุดด้วยข้อมูลที่ส่งผ่านไป

เนื่องจากการโทรกลับไม่ได้ดำเนินการในทันที แต่ในเวลาต่อมาสิ่งสำคัญคือต้องส่งต่อการอ้างอิงไปยังฟังก์ชันไม่ให้ดำเนินการ ดังนั้น

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

ดังนั้นเวลาส่วนใหญ่ (แต่ไม่เสมอไป) คุณจะfooไม่ผ่านfoo()

หวังว่าจะสมเหตุสมผลบ้าง เมื่อคุณพบสิ่งต่างๆเช่นนี้ที่ดูสับสน - ฉันขอแนะนำอย่างยิ่งให้อ่านเอกสารอย่างละเอียดเพื่ออย่างน้อยก็ทำความเข้าใจกับมัน มันจะทำให้คุณเป็นนักพัฒนาที่ดีขึ้นมาก