Làm cách nào để trả về phản hồi từ cuộc gọi không đồng bộ?


5511

Tôi có một hàm footạo một yêu cầu Ajax. Làm thế nào tôi có thể trả lại phản hồi từ foo?

Tôi đã thử trả về giá trị từ cuộc successgọi lại, cũng như gán phản hồi cho một biến cục bộ bên trong hàm và trả về giá trị đó, nhưng không có cách nào thực sự trả về phản hồi.

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`.

Câu trả lời:


5703

→ Để giải thích tổng quát hơn về hành vi không đồng bộ với các ví dụ khác nhau, vui lòng xem Tại sao biến của tôi không bị thay đổi sau khi tôi sửa đổi nó bên trong hàm? - Tham chiếu mã không đồng bộ

→ Nếu ​​bạn đã hiểu vấn đề, hãy bỏ qua các giải pháp có thể dưới đây.

Vấn đề

Chữ A trong Ajax là viết tắt của không đồng bộ . Điều đó có nghĩa là gửi yêu cầu (hay đúng hơn là nhận phản hồi) được đưa ra khỏi luồng thực thi thông thường. Trong ví dụ của bạn, $.ajaxtrả về ngay lập tức và câu lệnh tiếp theo return result;, được thực thi trước khi hàm bạn chuyển qua khi successgọi lại thậm chí được gọi.

Đây là một sự tương tự mà hy vọng làm cho sự khác biệt giữa dòng chảy đồng bộ và không đồng bộ rõ ràng hơn:

Đồng bộ

Hãy tưởng tượng bạn gọi điện thoại cho một người bạn và yêu cầu anh ta tìm kiếm thứ gì đó cho bạn. Mặc dù có thể mất một lúc, bạn đợi điện thoại và nhìn chằm chằm vào không gian, cho đến khi bạn của bạn đưa ra câu trả lời mà bạn cần.

Điều tương tự cũng xảy ra khi bạn thực hiện một cuộc gọi hàm chứa mã "bình thường":

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

var item = findItem();

// Do something with item
doSomethingElse();

Mặc dù findItemcó thể mất nhiều thời gian để thực thi, bất kỳ mã nào đến sau var item = findItem();phải đợi cho đến khi hàm trả về kết quả.

Không đồng bộ

Bạn gọi lại cho bạn của bạn vì lý do tương tự. Nhưng lần này bạn nói với anh ấy rằng bạn đang vội và anh ấy nên gọi lại cho bạn trên điện thoại di động của bạn. Bạn cúp máy, rời khỏi nhà và làm bất cứ điều gì bạn dự định làm. Khi bạn của bạn gọi lại cho bạn, bạn đang xử lý thông tin anh ấy đã cung cấp cho bạn.

Đó chính xác là những gì xảy ra khi bạn thực hiện một yêu cầu Ajax.

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

Thay vì chờ phản hồi, việc thực thi tiếp tục ngay lập tức và câu lệnh sau khi lệnh gọi Ajax được thực thi. Để nhận được phản hồi cuối cùng, bạn cung cấp một chức năng được gọi sau khi nhận được phản hồi, gọi lại (có thông báo gì không? Gọi lại ?). Bất kỳ câu lệnh nào đến sau cuộc gọi đó được thực thi trước khi gọi lại.


Các giải pháp)

Nắm bắt bản chất không đồng bộ của JavaScript! Mặc dù các hoạt động không đồng bộ nhất định cung cấp các đối tác đồng bộ ("Ajax"), nhưng nói chung, không khuyến khích sử dụng chúng, đặc biệt là trong bối cảnh trình duyệt.

Tại sao nó xấu khi bạn hỏi?

JavaScript chạy trong luồng UI của trình duyệt và bất kỳ quá trình chạy dài nào cũng sẽ khóa UI, khiến nó không phản hồi. Ngoài ra, có giới hạn trên về thời gian thực hiện đối với JavaScript và trình duyệt sẽ hỏi người dùng có tiếp tục thực hiện hay không.

Tất cả điều này là trải nghiệm người dùng thực sự xấu. Người dùng sẽ không thể biết liệu mọi thứ có hoạt động tốt hay không. Hơn nữa, hiệu quả sẽ tồi tệ hơn đối với người dùng có kết nối chậm.

Sau đây chúng ta sẽ xem xét ba giải pháp khác nhau, tất cả đều được xây dựng chồng lên nhau:

  • Hứa hẹn vớiasync/await (ES2017 +, khả dụng trong các trình duyệt cũ hơn nếu bạn sử dụng bộ chuyển mã hoặc trình tái tạo)
  • Gọi lại (phổ biến trong nút)
  • Hứa vớithen() (ES2015 +, khả dụng trong các trình duyệt cũ hơn nếu bạn sử dụng một trong nhiều thư viện hứa hẹn)

Cả ba đều có sẵn trong các trình duyệt hiện tại và nút 7+.


ES2017 +: Hứa hẹn với async/await

Phiên bản ECMAScript được phát hành năm 2017 đã giới thiệu hỗ trợ cấp cú pháp cho các chức năng không đồng bộ. Với sự giúp đỡ của asyncawait, bạn có thể viết không đồng bộ theo "kiểu đồng bộ". Mã vẫn không đồng bộ, nhưng dễ đọc / dễ hiểu hơn.

async/awaitxây dựng dựa trên lời hứa: một asyncchức năng luôn trả về một lời hứa. await"hủy bỏ" một lời hứa và dẫn đến giá trị của lời hứa đã được giải quyết hoặc ném lỗi nếu lời hứa bị từ chối.

Quan trọng: Bạn chỉ có thể sử dụng awaitbên trong một asyncchức năng. Ngay bây giờ, cấp cao nhất awaitchưa được hỗ trợ, do đó bạn có thể phải tạo một IIFE async ( Biểu thức hàm được gọi ngay lập tức ) để bắt đầu một asyncbối cảnh.

Bạn có thể đọc thêm về asyncawaittrên MDN.

Dưới đây là một ví dụ được xây dựng dựa trên độ trễ trên:

// 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);
})();

Phiên bản trình duyệtnút hiện tại hỗ trợ async/await. Bạn cũng có thể hỗ trợ các môi trường cũ hơn bằng cách chuyển đổi mã của mình sang ES5 với sự trợ giúp của trình tái tạo (hoặc các công cụ sử dụng trình tái tạo, chẳng hạn như Babel ).


Để các hàm chấp nhận cuộc gọi lại

Một cuộc gọi lại chỉ đơn giản là một chức năng được chuyển đến một chức năng khác. Hàm khác có thể gọi hàm được truyền bất cứ khi nào nó sẵn sàng. Trong ngữ cảnh của một quy trình không đồng bộ, cuộc gọi lại sẽ được gọi bất cứ khi nào quá trình không đồng bộ được thực hiện. Thông thường, kết quả được chuyển đến cuộc gọi lại.

Trong ví dụ của câu hỏi, bạn có thể foochấp nhận gọi lại và sử dụng nó làm successcuộc gọi lại. Vậy đây

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

trở thành

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

Ở đây chúng tôi đã định nghĩa hàm "nội tuyến" nhưng bạn có thể vượt qua bất kỳ tham chiếu chức năng nào:

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

foo(myCallback);

foo chính nó được định nghĩa như sau:

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

callbacksẽ đề cập đến chức năng chúng ta chuyển đến fookhi chúng ta gọi nó và chúng ta chỉ cần chuyển nó sang success. Tức là một khi yêu cầu Ajax thành công, $.ajaxsẽ gọi callbackvà chuyển phản hồi cho cuộc gọi lại (có thể được gọi bằng result, vì đây là cách chúng tôi đã xác định cuộc gọi lại).

Bạn cũng có thể xử lý phản hồi trước khi chuyển nó đến cuộc gọi lại:

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

Viết mã bằng cách gọi lại dễ dàng hơn có vẻ. Rốt cuộc, JavaScript trong trình duyệt được điều khiển rất nhiều sự kiện (các sự kiện DOM). Nhận được phản hồi Ajax không gì khác ngoài một sự kiện.
Khó khăn có thể phát sinh khi bạn phải làm việc với mã của bên thứ ba, nhưng hầu hết các vấn đề có thể được giải quyết chỉ bằng cách suy nghĩ thông qua dòng ứng dụng.


ES2015 +: Hứa với sau đó ()

Các Promise API là một tính năng mới của ECMAScript 6 (ES2015), nhưng nó có tốt hỗ trợ trình duyệt rồi. Ngoài ra còn có nhiều thư viện triển khai API Promise tiêu chuẩn và cung cấp các phương thức bổ sung để dễ dàng sử dụng và cấu thành các chức năng không đồng bộ (ví dụ: bluebird ).

Lời hứa là container cho các giá trị trong tương lai . Khi lời hứa nhận được giá trị (nó đã được giải quyết ) hoặc khi nó bị hủy ( bị từ chối ), nó sẽ thông báo cho tất cả "người nghe" của mình, những người muốn truy cập giá trị này.

Ưu điểm so với các cuộc gọi lại đơn giản là chúng cho phép bạn tách mã của bạn và chúng dễ dàng soạn thảo hơn.

Đây là một ví dụ đơn giản về việc sử dụng một lời hứa:

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

Áp dụng cho lệnh gọi Ajax của chúng tôi, chúng tôi có thể sử dụng các lời hứa như thế này:

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

Mô tả tất cả các lợi thế mà lời hứa cung cấp nằm ngoài phạm vi của câu trả lời này, nhưng nếu bạn viết mã mới, bạn nên nghiêm túc xem xét chúng. Họ cung cấp một sự trừu tượng hóa và phân tách mã của bạn.

Thông tin thêm về lời hứa: Đá HTML5 - Lời hứa JavaScript

Lưu ý bên lề: Các đối tượng bị trì hoãn của jQuery

Các đối tượng bị trì hoãn là việc thực hiện các lời hứa tùy chỉnh của jQuery (trước khi API Promise được chuẩn hóa). Họ hành xử gần giống như những lời hứa nhưng để lộ một API hơi khác.

Mọi phương thức Ajax của jQuery đã trả về một "đối tượng hoãn lại" (thực ra là một lời hứa về một đối tượng bị trì hoãn) mà bạn có thể trả về từ hàm của mình:

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

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

Lưu ý phụ: Promise gotchas

Hãy nhớ rằng những lời hứa và các đối tượng bị trì hoãn chỉ là vật chứa cho một giá trị trong tương lai, bản thân chúng không phải là giá trị. Ví dụ: giả sử bạn có những điều sau đây:

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
}

Mã này hiểu sai các vấn đề không đồng bộ ở trên. Cụ thể, $.ajax()không đóng băng mã trong khi nó kiểm tra trang '/ password' trên máy chủ của bạn - nó sẽ gửi yêu cầu đến máy chủ và trong khi chờ, nó sẽ trả về ngay một đối tượng Trì hoãn Ajax, chứ không phải phản hồi từ máy chủ. Điều đó có nghĩa là ifcâu lệnh sẽ luôn lấy đối tượng Trì hoãn này, coi nó như truevà tiến hành như thể người dùng đã đăng nhập. Không tốt.

Nhưng cách khắc phục rất dễ:

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

Không được đề xuất: Các cuộc gọi "Ajax" đồng bộ

Như tôi đã đề cập, một số hoạt động không đồng bộ (!) Có các đối tác đồng bộ. Tôi không ủng hộ việc sử dụng chúng, nhưng để hoàn thiện, đây là cách bạn sẽ thực hiện một cuộc gọi đồng bộ:

Không có jQuery

Nếu bạn trực tiếp sử dụng một XMLHTTPRequestđối tượng, chuyển falselàm đối số thứ ba cho .open.

jQuery

Nếu bạn sử dụng jQuery , bạn có thể đặt asynctùy chọn thành false. Lưu ý rằng tùy chọn này không được dùng kể từ jQuery 1.8. Sau đó, bạn vẫn có thể sử dụng một successcuộc gọi lại hoặc truy cập vào thuộc responseTexttính của đối tượng jqXHR :

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

Nếu bạn sử dụng bất kỳ phương thức jQuery Ajax nào khác, chẳng hạn như $.get, $.getJSONv.v., bạn phải thay đổi nó thành $.ajax(vì bạn chỉ có thể truyền tham số cấu hình cho $.ajax).

Đứng lên! Không thể thực hiện một yêu cầu JSONP đồng bộ . JSONP về bản chất luôn luôn không đồng bộ (một lý do nữa để thậm chí không xem xét tùy chọn này).


74
@ Mẹ: Nếu bạn muốn sử dụng jQuery, bạn phải đưa nó vào. Vui lòng tham khảo docs.jquery.com/Tutorials:Getting_Started_with_jQuery .
Felix Kling

11
Trong Giải pháp 1, phụ jQuery, tôi không thể hiểu được dòng này: If you use any other jQuery AJAX method, such as $.get, $.getJSON, etc., you have them to $.ajax.(Vâng, tôi nhận ra nick của tôi là một sự mỉa mai trong trường hợp này)
cssyphus

32
@gibberish: Mmmh, tôi không biết làm thế nào nó có thể được làm rõ ràng hơn. Bạn có thấy cách foogọi và hàm được truyền cho nó ( foo(function(result) {....});) không? resultđược sử dụng bên trong hàm này và là đáp ứng của yêu cầu Ajax. Để chỉ chức năng này, tham số đầu tiên của foo được gọi callbackvà gán cho successthay vì một chức năng ẩn danh. Vì vậy, $.ajaxsẽ gọi callbackkhi yêu cầu thành công. Tôi đã cố gắng để giải thích nó một chút nữa.
Felix Kling

43
Trò chuyện cho câu hỏi này đã chết nên tôi không chắc chắn đề xuất thay đổi được phác thảo ở đâu, nhưng tôi đề xuất: 1) Thay đổi phần đồng bộ thành một cuộc thảo luận đơn giản về lý do tại sao nó không có ví dụ về cách thực hiện. 2) Xóa / hợp nhất các ví dụ gọi lại để chỉ hiển thị cách tiếp cận Trì hoãn linh hoạt hơn, mà tôi nghĩ cũng có thể dễ thực hiện hơn một chút đối với những người học Javascript.
Chris Moschini

14
@Jessi: Tôi nghĩ bạn đã hiểu nhầm đó là một phần của câu trả lời. Bạn không thể sử dụng $.getJSONnếu bạn muốn yêu cầu Ajax được đồng bộ. Tuy nhiên, bạn không nên sự kiện muốn yêu cầu được đồng bộ, do đó không áp dụng. Bạn nên sử dụng các cuộc gọi lại hoặc lời hứa để xử lý phản hồi, vì nó được giải thích trước đó trong câu trả lời.
Felix Kling

1071

Nếu bạn không sử dụng jQuery trong mã của mình, câu trả lời này là dành cho bạn

Mã của bạn phải là một cái gì đó dọc theo dòng này:

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 đã làm rất tốt khi viết câu trả lời cho những người sử dụng jQuery cho AJAX, tôi đã quyết định cung cấp một giải pháp thay thế cho những người không biết.

( Lưu ý, đối với những người sử dụng fetchAPI mới , Angular hoặc lời hứa tôi đã thêm một câu trả lời khác bên dưới )


Những gì bạn đang đối mặt

Đây là một bản tóm tắt ngắn gọn về "Giải thích vấn đề" từ câu trả lời khác, nếu bạn không chắc chắn sau khi đọc nó, hãy đọc nó.

Chữ A trong AJAX là viết tắt của không đồng bộ . Điều đó có nghĩa là gửi yêu cầu (hay đúng hơn là nhận phản hồi) được đưa ra khỏi luồng thực thi thông thường. Trong ví dụ của bạn, .sendtrả về ngay lập tức và câu lệnh tiếp theo return result;, được thực thi trước khi hàm bạn chuyển qua khi successgọi lại thậm chí được gọi.

Điều này có nghĩa là khi bạn quay lại, trình nghe bạn đã xác định chưa thực thi, điều đó có nghĩa là giá trị bạn trả về chưa được xác định.

Đây là một tương tự đơn giản

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

(Vĩ cầm)

Giá trị atrả về là undefineddo a=5phần chưa được thực hiện. AJAX hoạt động như thế này, bạn đang trả lại giá trị trước khi máy chủ có cơ hội cho trình duyệt của bạn biết giá trị đó là gì.

Một giải pháp khả thi cho vấn đề này là mã hóa lại chủ động , cho chương trình của bạn biết phải làm gì khi tính toán hoàn tất.

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

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

Điều này được gọi là CPS . Về cơ bản, chúng tôi sẽ chuyển getFivemột hành động để thực hiện khi nó hoàn thành, chúng tôi sẽ nói cho mã của mình cách phản ứng khi một sự kiện hoàn thành (như cuộc gọi AJAX của chúng tôi hoặc trong trường hợp này là hết thời gian chờ).

Cách sử dụng sẽ là:

getFive(onComplete);

Mà nên cảnh báo "5" cho màn hình. (Câu đố) .

Phương pháp khả thi

Về cơ bản có hai cách để giải quyết điều này:

  1. Thực hiện cuộc gọi AJAX đồng bộ (hãy gọi nó là SJAX).
  2. Cấu trúc lại mã của bạn để hoạt động đúng với các cuộc gọi lại.

1. AJAX đồng bộ - Đừng làm điều đó !!

Đối với AJAX đồng bộ, đừng làm điều đó! Câu trả lời của Felix nêu lên một số lập luận thuyết phục về lý do tại sao đó là một ý tưởng tồi. Tóm lại, nó sẽ đóng băng trình duyệt của người dùng cho đến khi máy chủ trả lời phản hồi và tạo ra trải nghiệm người dùng rất tệ. Đây là một bản tóm tắt ngắn khác được lấy từ MDN về lý do:

XMLHttpRequest hỗ trợ cả truyền thông đồng bộ và không đồng bộ. Tuy nhiên, nói chung, các yêu cầu không đồng bộ nên được ưu tiên hơn các yêu cầu đồng bộ vì lý do hiệu suất.

Nói tóm lại, các yêu cầu đồng bộ chặn việc thực thi mã ... ... điều này có thể gây ra sự cố nghiêm trọng ...

Nếu bạn phải làm điều đó, bạn có thể vượt qua một lá cờ: Đây là cách:

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. Tái cấu trúc mã

Hãy để chức năng của bạn chấp nhận một cuộc gọi lại. Trong ví dụ mã foocó thể được thực hiện để chấp nhận một cuộc gọi lại. Chúng tôi sẽ nói cho mã của chúng tôi cách phản ứng khi foohoàn thành.

Vì thế:

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

Trở thành:

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

Ở đây chúng ta đã truyền một hàm ẩn danh, nhưng chúng ta có thể dễ dàng chuyển một tham chiếu đến một hàm hiện có, làm cho nó trông giống như:

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

Để biết thêm chi tiết về cách thiết kế gọi lại này được thực hiện, hãy kiểm tra câu trả lời của Felix.

Bây giờ, hãy xác định foo chính nó để hành động phù hợp

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

(vĩ cầm)

Bây giờ chúng ta đã làm cho hàm foo của chúng ta chấp nhận một hành động để chạy khi AJAX hoàn thành thành công, chúng ta có thể mở rộng điều này hơn nữa bằng cách kiểm tra xem trạng thái phản hồi không phải là 200 và hành động tương ứng (tạo một trình xử lý lỗi và như vậy). Giải quyết hiệu quả vấn đề của chúng tôi.

Nếu bạn vẫn gặp khó khăn trong việc hiểu điều này, hãy đọc hướng dẫn bắt đầu AJAX tại MDN.


20
"các yêu cầu đồng bộ chặn việc thực thi mã và có thể rò rỉ bộ nhớ và sự kiện" làm thế nào một yêu cầu đồng bộ có thể rò rỉ bộ nhớ?
Matthew G

10
@MatthewG Tôi đã thêm một tiền thưởng vào câu hỏi này , tôi sẽ xem những gì tôi có thể câu được. Tôi đang loại bỏ trích dẫn từ câu trả lời trong thời gian trung bình.
Benjamin Gruenbaum

17
Chỉ để tham khảo, XHR 2 cho phép chúng tôi sử dụng onloadtrình xử lý, chỉ kích hoạt khi readyState4. Tất nhiên, nó không được hỗ trợ trong IE8. (iirc, có thể cần xác nhận.)
Florian Margaine

9
Giải thích của bạn về cách vượt qua chức năng ẩn danh dưới dạng gọi lại là hợp lệ nhưng gây hiểu nhầm. Ví dụ var bar = foo (); đang yêu cầu một biến được xác định, trong khi foo đề xuất của bạn (funcim () {}); không xác định thanh
Robbie Averill

396

XMLHttpRequest 2 (trước hết hãy đọc câu trả lời từ Benjamin Gruenbaum & Felix Kling )

Nếu bạn không sử dụng jQuery và muốn có một XMLHttpRequest 2 ngắn đẹp, hoạt động trên các trình duyệt hiện đại và cả trên các trình duyệt di động, tôi khuyên bạn nên sử dụng nó theo cách này:

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

Bạn có thể thấy:

  1. Nó ngắn hơn tất cả các chức năng khác được liệt kê.
  2. Cuộc gọi lại được đặt trực tiếp (vì vậy không có thêm các lần đóng không cần thiết).
  3. Nó sử dụng tải mới (vì vậy bạn không phải kiểm tra trạng thái sẵn sàng &&)
  4. Có một số tình huống khác mà tôi không nhớ làm cho XMLHttpRequest 1 gây khó chịu.

Có hai cách để nhận được phản hồi của lệnh gọi Ajax này (ba cách sử dụng tên var XMLHttpRequest):

Điều đơn giản nhất:

this.response

Hoặc nếu vì lý do nào đó bạn bind()gọi lại cho một lớp:

e.target.response

Thí dụ:

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

Hoặc (một trong những chức năng ẩn danh tốt hơn luôn là một vấn đề):

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

Không có gì dễ dàng hơn.

Bây giờ một số người có thể sẽ nói rằng tốt hơn là sử dụng onreadystatechange hoặc thậm chí tên biến XMLHttpRequest. Sai rồi.

Kiểm tra các tính năng nâng cao của XMLHttpRequest

Nó hỗ trợ tất cả * trình duyệt hiện đại. Và tôi có thể xác nhận rằng tôi đang sử dụng phương pháp này kể từ khi XMLHttpRequest 2 tồn tại. Tôi chưa bao giờ có bất kỳ loại vấn đề nào trên tất cả các trình duyệt tôi sử dụng.

onreadystatechange chỉ hữu ích nếu bạn muốn có được các tiêu đề ở trạng thái 2.

Sử dụng XMLHttpRequesttên biến là một lỗi lớn khác khi bạn cần thực hiện cuộc gọi lại bên trong việc đóng onload / oreadystatechange nếu không bạn đã mất nó.


Bây giờ nếu bạn muốn một cái gì đó phức tạp hơn bằng cách sử dụng bài đăng và FormData, bạn có thể dễ dàng mở rộng chức năng này:

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

Một lần nữa ... đó là một chức năng rất ngắn, nhưng nó có được & đăng.

Ví dụ về việc sử dụng:

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

Hoặc vượt qua một phần tử dạng đầy đủ ( document.getElementsByTagName('form')[0]):

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

Hoặc đặt một số giá trị tùy chỉnh:

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

Như bạn có thể thấy tôi đã không thực hiện đồng bộ hóa ... đó là một điều tồi tệ.

Đã nói rằng ... tại sao không làm điều đó một cách dễ dàng?


Như đã đề cập trong bình luận, việc sử dụng lỗi && đồng bộ hoàn toàn phá vỡ điểm của câu trả lời. Đó là một cách ngắn hay để sử dụng Ajax theo cách thích hợp?

Xử lý lỗi

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

Trong đoạn script trên, bạn có một trình xử lý lỗi được xác định tĩnh để nó không ảnh hưởng đến hàm. Trình xử lý lỗi cũng có thể được sử dụng cho các chức năng khác.

Nhưng để thực sự nhận ra một lỗi, cách duy nhất là viết một URL sai trong trường hợp mọi trình duyệt đều đưa ra một lỗi.

Trình xử lý lỗi có thể hữu ích nếu bạn đặt tiêu đề tùy chỉnh, đặt answerType thành bộ đệm mảng blob hoặc bất cứ điều gì ...

Ngay cả khi bạn vượt qua 'POSTAPAPAP' như phương thức, nó sẽ không gây ra lỗi.

Ngay cả khi bạn vượt qua 'fdggdgilfdghfldj' dưới dạng formdata, nó sẽ không gây ra lỗi.

Trong trường hợp đầu tiên lỗi là bên trong displayAjax()dưới this.statusTextnhư Method not Allowed.

Trong trường hợp thứ hai, nó chỉ đơn giản hoạt động. Bạn phải kiểm tra ở phía máy chủ nếu bạn chuyển đúng dữ liệu bài đăng.

tên miền chéo không được phép ném lỗi tự động.

Trong phản hồi lỗi, không có mã lỗi.

Chỉ có this.typecái được đặt thành lỗi.

Tại sao thêm một trình xử lý lỗi nếu bạn hoàn toàn không kiểm soát được lỗi? Hầu hết các lỗi được trả lại bên trong này trong chức năng gọi lại displayAjax().

Vì vậy: Không cần kiểm tra lỗi nếu bạn có thể sao chép và dán URL đúng cách. ;)

PS: Là thử nghiệm đầu tiên tôi đã viết x ('x', displayAjax) ..., và nó hoàn toàn có phản hồi ... ??? Vì vậy, tôi đã kiểm tra thư mục chứa HTML và có một tệp có tên 'x.xml'. Vì vậy, ngay cả khi bạn quên phần mở rộng của tệp XMLHttpRequest 2 SILL TÌM NÓ . Tôi LOL


Đọc một tập tin đồng bộ

Đừng làm vậy.

Nếu bạn muốn chặn trình duyệt trong một thời gian, hãy tải một .txttệp lớn đồng bộ đẹp.

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

Bây giờ bạn có thể làm

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

Không có cách nào khác để làm điều này theo cách không đồng bộ. (Vâng, với vòng lặp setTimeout ... nhưng nghiêm túc chứ?)

Một điểm khác là ... nếu bạn làm việc với API hoặc chỉ các tệp trong danh sách của riêng bạn hoặc bất cứ điều gì bạn luôn sử dụng các chức năng khác nhau cho mỗi yêu cầu ...

Chỉ khi bạn có một trang nơi bạn tải luôn cùng một XML / JSON hoặc bất cứ thứ gì bạn chỉ cần một hàm. Trong trường hợp đó, hãy sửa đổi một chút hàm Ajax và thay thế b bằng hàm đặc biệt của bạn.


Các chức năng trên là để sử dụng cơ bản.

Nếu bạn muốn loại bỏ chức năng ...

Vâng, bạn có thể.

Tôi đang sử dụng rất nhiều API và một trong những hàm đầu tiên tôi tích hợp vào mọi trang HTML là hàm Ajax đầu tiên trong câu trả lời này, chỉ với GET ...

Nhưng bạn có thể làm rất nhiều thứ với XMLHttpRequest 2:

Tôi đã tạo một trình quản lý tải xuống (sử dụng các phạm vi ở cả hai phía với sơ yếu lý lịch, trình quay phim, hệ thống tập tin), các trình chuyển đổi hình ảnh khác nhau bằng cách sử dụng canvas, điền vào cơ sở dữ liệu SQL web với các cơ sở 64 và nhiều hơn nữa ... Nhưng trong những trường hợp này, bạn chỉ nên tạo một hàm cho điều đó mục đích ... đôi khi bạn cần một blob, bộ đệm mảng, bạn có thể đặt tiêu đề, ghi đè mimetype và có nhiều hơn nữa ...

Nhưng câu hỏi ở đây là làm thế nào để trả về phản hồi Ajax ... (Tôi đã thêm một cách dễ dàng.)


15
Mặc dù câu trả lời này rất hay (Và tất cả chúng ta đều yêu thích XHR2 và đăng dữ liệu tệp và dữ liệu nhiều phần là hoàn toàn tuyệt vời) - điều này cho thấy đường cú pháp để đăng XHR bằng JavaScript - bạn có thể muốn đưa bài này vào bài đăng trên blog (tôi thích nó) hoặc thậm chí trong một thư viện (không chắc chắn về tên x, ajaxhoặc xhrcó thể đẹp hơn :)). Tôi không thấy cách giải quyết trả lời phản hồi từ cuộc gọi AJAX. (ai đó vẫn có thể làm var res = x("url")và không hiểu tại sao nó không hoạt động;)). Bên cạnh đó - sẽ rất tuyệt nếu bạn quay lại ctừ phương thức để người dùng có thể kết nối, errorv.v.
Benjamin Gruenbaum

25
2.ajax is meant to be async.. so NO var res=x('url')..Đó là toàn bộ vấn đề của câu hỏi và câu trả lời này :)
Benjamin Gruenbaum

3
Tại sao lại có tham số 'c' trong các hàm, nếu trên dòng đầu tiên bạn ghi đè bất kỳ giá trị nào nó có? tui bỏ lỡ điều gì vậy?
Brian H.

2
Bạn có thể sử dụng các tham số làm trình giữ chỗ để tránh viết nhiều lần "var"
cocco

11
@cocco Vậy bạn đã viết mã sai, không thể đọc được trong câu trả lời SO để lưu một vài tổ hợp phím? Xin đừng làm vậy.
đá

316

Nếu bạn đang sử dụng lời hứa, câu trả lời này là dành cho bạn.

Điều này có nghĩa là AngularJS, jQuery (đã hoãn lại), thay thế XHR gốc (tìm nạp), EmberJS, lưu BackboneJS hoặc bất kỳ thư viện nút nào trả về lời hứa.

Mã của bạn phải là một cái gì đó dọc theo dòng này:

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 đã làm rất tốt khi viết câu trả lời cho những người sử dụng jQuery với các cuộc gọi lại cho AJAX. Tôi có một câu trả lời cho XHR bản địa. Câu trả lời này dành cho việc sử dụng chung các lời hứa hoặc ở mặt trước hoặc phụ trợ.


Vấn đề cốt lõi

Mô hình đồng thời JavaScript trong trình duyệt và trên máy chủ có NodeJS / io.js không đồng bộphản ứng .

Bất cứ khi nào bạn gọi một phương thức trả về một lời hứa, các thentrình xử lý luôn được thực thi không đồng bộ - nghĩa là, sau mã bên dưới chúng không nằm trong một .thentrình xử lý.

Điều này có nghĩa khi bạn trở về datacác thenhandler bạn đã xác định không thực hiện được nêu ra. Đến lượt điều này có nghĩa là giá trị bạn trả về chưa được đặt thành giá trị chính xác theo thời gian.

Đây là một tương tự đơn giản cho vấn đề:

    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

Giá trị của dataundefineddo data = 5phần chưa được thực hiện. Nó có thể sẽ thực thi trong một giây nhưng vào thời điểm đó, nó không liên quan đến giá trị được trả về.

Vì hoạt động chưa xảy ra (AJAX, cuộc gọi máy chủ, IO, bộ đếm thời gian) bạn sẽ trả lại giá trị trước khi yêu cầu có cơ hội cho mã của bạn biết giá trị đó là gì.

Một giải pháp khả thi cho vấn đề này là mã hóa lại chủ động , cho chương trình của bạn biết phải làm gì khi tính toán hoàn tất. Hứa chủ động kích hoạt điều này bằng cách tạm thời (nhạy cảm với thời gian) trong tự nhiên.

Tóm tắt nhanh về những lời hứa

Một lời hứa là một giá trị theo thời gian . Hứa có trạng thái, chúng bắt đầu như đang chờ xử lý mà không có giá trị và có thể giải quyết:

  • hoàn thành nghĩa là tính toán hoàn thành thành công.
  • từ chối có nghĩa là tính toán thất bại.

Một lời hứa chỉ có thể thay đổi trạng thái một lần sau đó nó sẽ luôn ở cùng một trạng thái mãi mãi. Bạn có thể đính kèm thentrình xử lý để hứa sẽ trích xuất giá trị của chúng và xử lý lỗi. thenxử lý cho phép xâu chuỗi các cuộc gọi. Lời hứa được tạo bằng cách sử dụng API trả lại chúng . Ví dụ, lời hứa thay thế AJAX hiện đại hơn fetchhoặc $.gettrả lại của jQuery .

Khi chúng tôi gọi .thenmột lời hứa và trả lại một cái gì đó từ nó - chúng tôi nhận được một lời hứa cho giá trị được xử lý . Nếu chúng ta trả lại một lời hứa khác, chúng ta sẽ nhận được những điều tuyệt vời, nhưng hãy giữ ngựa của chúng ta.

Với lời hứa

Chúng ta hãy xem làm thế nào chúng ta có thể giải quyết vấn đề trên với những lời hứa. Trước tiên, hãy chứng minh sự hiểu biết của chúng tôi về các trạng thái hứa từ trên bằng cách sử dụng hàm tạo Promise để tạo hàm trì hoãn:

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

Bây giờ, sau khi chúng tôi chuyển đổi setTimeout để sử dụng lời hứa, chúng tôi có thể sử dụng thenđể làm cho nó được tính:

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

Về cơ bản, thay vì trả lại một giá trị mà chúng tôi không thể làm được vì mô hình đồng thời - chúng tôi trả lại một wrapper cho một giá trị mà chúng ta có thể unwrap với then. Nó giống như một cái hộp bạn có thể mở then.

Áp dụng điều này

Điều này tương tự với lệnh gọi API ban đầu của bạn, bạn có thể:

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`
})

Vì vậy, điều này làm việc như là tốt. Chúng tôi đã học được rằng chúng tôi không thể trả về các giá trị từ các cuộc gọi đã không đồng bộ nhưng chúng tôi có thể sử dụng các lời hứa và xâu chuỗi chúng để thực hiện xử lý. Bây giờ chúng ta biết làm thế nào để trả về phản hồi từ một cuộc gọi không đồng bộ.

ES2015 (ES6)

ES6 giới thiệu các máy phát điện là các chức năng có thể quay trở lại ở giữa và sau đó tiếp tục điểm mà chúng đã ở. Điều này thường hữu ích cho các chuỗi, ví dụ:

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

Là một hàm trả về một iterator qua chuỗi 1,2,3,3,3,3,....có thể được lặp lại. Mặc dù điều này là thú vị của riêng nó và mở ra nhiều khả năng có một trường hợp đặc biệt thú vị.

Nếu chuỗi chúng tôi tạo ra là một chuỗi các hành động chứ không phải là số - chúng tôi có thể tạm dừng chức năng bất cứ khi nào một hành động được thực hiện và chờ đợi nó trước khi chúng tôi tiếp tục chức năng. Vì vậy, thay vì một chuỗi các số, chúng ta cần một chuỗi các giá trị trong tương lai - đó là: những lời hứa.

Thủ thuật có phần phức tạp nhưng rất mạnh mẽ này cho phép chúng ta viết mã không đồng bộ một cách đồng bộ. Có một số "vận động viên" làm điều này cho bạn, viết một là một vài dòng mã ngắn nhưng nằm ngoài phạm vi của câu trả lời này. Tôi sẽ sử dụng Bluebird Promise.coroutineở đây, nhưng có các trình bao bọc khác như cohoặc 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
});

Phương pháp này trả về một lời hứa mà chúng ta có thể tiêu thụ từ các coroutines khác. Ví dụ:

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)

Trong ES7, điều này được chuẩn hóa hơn nữa, có một số đề xuất ngay bây giờ nhưng trong tất cả chúng, bạn có thể awaithứa. Đây chỉ là "đường" (cú pháp đẹp hơn) cho đề xuất ES6 ở trên bằng cách thêm asyncawaittừ khóa. Làm ví dụ trên:

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
}

Nó vẫn trả lại một lời hứa giống nhau :)


Đây phải là câu trả lời được chấp nhận. +1 cho async / await (mặc dù chúng ta có nên không return await data.json();?)
Lewis Donovan

247

Bạn đang sử dụng Ajax không chính xác. Ý tưởng không phải là nó trả lại bất cứ thứ gì, mà thay vào đó, chuyển dữ liệu sang một thứ gọi là chức năng gọi lại, xử lý dữ liệu.

Đó là:

function handleData( responseData ) {

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

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

Trả lại bất cứ điều gì trong trình xử lý trình sẽ không làm gì cả. Thay vào đó, bạn phải xử lý dữ liệu hoặc thực hiện những gì bạn muốn với dữ liệu trực tiếp bên trong chức năng thành công.


13
Câu trả lời này là hoàn toàn ngữ nghĩa ... phương pháp thành công của bạn chỉ là một cuộc gọi lại trong một cuộc gọi lại. Bạn chỉ có thể có success: handleDatavà nó sẽ làm việc.
Jacques ャ ッ

5
Và nếu bạn muốn trả lại "answerData" bên ngoài "handleData" ... :) ... bạn sẽ làm thế nào ...? ... gây ra một sự trở lại đơn giản sẽ đưa nó trở lại cuộc gọi lại "thành công" của ajax ... và không nằm ngoài "handleData" ...
pesho hristov

@Jacques & @pesho hristov Bạn đã bỏ lỡ điểm này. Trình xử lý không phải là successphương thức, đó là phạm vi xung quanh $.ajax.
travnik

@tra Mand Tôi không bỏ lỡ điều đó. Nếu bạn lấy nội dung của handleData và đưa nó vào phương thức thành công, nó sẽ hoạt động giống hệt ...
Jacques ャ ッ ク

234

Giải pháp đơn giản nhất là tạo một hàm JavaScript và gọi nó cho cuộc successgọi lại Ajax .

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

3
Tôi không biết ai đã bình chọn nó tiêu cực. Nhưng đây là một công việc xung quanh đã hoạt động trong thực tế tôi đã sử dụng phương pháp này để tạo ra một ứng dụng hoàn chỉnh. Jquery.ajax không trả lại dữ liệu để sử dụng phương pháp trên tốt hơn. Nếu nó sai thì hãy giải thích và đề xuất cách tốt hơn để làm điều đó.
Hemant Bavle

11
Xin lỗi, tôi quên để lại một bình luận (tôi thường làm!). Tôi đánh giá thấp nó. Downvote không chỉ ra sự đúng đắn hoặc thiếu thực tế, chúng chỉ ra sự hữu ích trong bối cảnh hoặc thiếu. Tôi không thấy câu trả lời của bạn hữu ích được đưa ra bởi Felix đã giải thích điều này chỉ chi tiết hơn nhiều. Bên cạnh đó, tại sao bạn lại xâu chuỗi câu trả lời nếu là JSON?
Benjamin Gruenbaum

5
ok .. @Benjamin tôi đã sử dụng Stringify, để chuyển đổi một đối tượng JSON thành chuỗi. Và cảm ơn đã làm rõ quan điểm của bạn. Sẽ ghi nhớ để gửi câu trả lời công phu hơn.
Hemant Bavle

Và nếu bạn muốn trả lại "answerObj" bên ngoài "thành công" ... :) ... bạn sẽ làm thế nào ...? ... gây ra một sự trở lại đơn giản sẽ đưa nó trở lại cuộc gọi lại "thành công" của ajax ... và không nằm ngoài "thành công", ...
pesho hristov

221

Tôi sẽ trả lời với một truyện tranh vẽ tay kinh khủng. Hình ảnh thứ hai là lý do tại sao resultundefinedtrong ví dụ mã của bạn.

nhập mô tả hình ảnh ở đây


32
Một bức tranh đáng giá cả ngàn lời nói , Người A - Hỏi người B chi tiết để sửa xe của anh ta, đến lượt Người B - Thực hiện Cuộc gọi Ajax và chờ phản hồi từ máy chủ để biết chi tiết sửa xe, khi nhận được phản hồi, chức năng Ajax Thành công gọi cho Người Hàm B và chuyển phản hồi làm đối số cho nó, Người A nhận được câu trả lời.
shaijut

10
Sẽ thật tuyệt nếu bạn thêm các dòng mã với mỗi hình ảnh để minh họa các khái niệm.
Hassan Baig

1
Trong khi đó, anh chàng với chiếc xe bị kẹt bên vệ đường. Anh ta yêu cầu chiếc xe được cố định trước khi tiếp tục. Bây giờ anh ta đang ở một mình bên đường chờ đợi ... Anh ta thà nghe điện thoại chờ thay đổi trạng thái nhưng thợ máy sẽ không làm điều đó ... Thợ máy nói rằng anh ta phải tiếp tục công việc của mình và không thể chỉ đơn giản là đi chơi trên điện thoại. Cơ khí hứa rằng anh sẽ gọi lại cho anh ngay khi có thể. Sau khoảng 4 giờ, anh chàng bỏ cuộc và gọi Uber. - Ví dụ về thời gian chờ.
barrypicker

@barrypicker :-D Rực rỡ!
Julian Fahrenkrug

159

Góc1

Đối với những người đang sử dụng AngularJS , có thể xử lý tình huống này bằng cách sử dụng Promises.

Ở đây nó nói,

Lời hứa có thể được sử dụng để hủy bỏ các chức năng không đồng bộ và cho phép một chuỗi nhiều chức năng lại với nhau.

Bạn có thể tìm thấy một lời giải thích tốt đẹp ở đây cũng.

Ví dụ tìm thấy trong các tài liệu được đề cập dưới đây.

  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 và sau đó

Trong Angular2với nhìn vào ví dụ sau, nhưng nó khuyến khích để sử dụng Observablesvới Angular2.

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

}

Bạn có thể tiêu thụ theo cách này,

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

Xem bài gốc ở đây. Nhưng Typecript không hỗ trợ Promise es6 bản địa , nếu bạn muốn sử dụng nó, bạn có thể cần plugin cho điều đó.

Ngoài ra đây là thông số kỹ thuật hứa hẹn xác định ở đây.


15
Điều này không giải thích làm thế nào lời hứa sẽ giải quyết vấn đề này mặc dù.
Benjamin Gruenbaum

4
Cả hai phương thức jQuery và fetch đều trả lại lời hứa. Tôi sẽ đề nghị xem lại câu trả lời của bạn. Mặc dù jQuery không hoàn toàn giống nhau (sau đó là có, nhưng không bắt được).
Tracker1

153

Hầu hết các câu trả lời ở đây đều đưa ra các đề xuất hữu ích khi bạn có một thao tác async duy nhất, nhưng đôi khi, điều này xuất hiện khi bạn cần thực hiện một thao tác không đồng bộ cho mỗi mục trong một mảng hoặc cấu trúc giống như danh sách khác. Sự cám dỗ là để làm điều này:

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

Thí dụ:

Lý do không hoạt động là các cuộc gọi lại từ doSomethingAsyncchưa chạy đến khi bạn cố gắng sử dụng kết quả.

Vì vậy, nếu bạn có một mảng (hoặc danh sách một số loại) và muốn thực hiện các thao tác không đồng bộ cho mỗi mục, bạn có hai tùy chọn: Thực hiện các thao tác song song (chồng chéo) hoặc nối tiếp (lần lượt theo thứ tự).

Song song, tương đông

Bạn có thể bắt đầu tất cả chúng và theo dõi xem có bao nhiêu cuộc gọi lại mà bạn mong đợi và sau đó sử dụng kết quả khi bạn nhận được nhiều cuộc gọi lại:

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

Thí dụ:

(Chúng ta có thể loại bỏ expectingvà chỉ sử dụng results.length === theArray.length, nhưng điều đó khiến chúng ta mở ra khả năng rằngtheArray bị thay đổi trong khi các cuộc gọi vẫn còn tồn tại ...)

Lưu ý cách chúng ta sử dụng indextừ forEachđể lưu kết quả trongresults cùng vị trí với mục nhập liên quan đến nó, ngay cả khi kết quả không theo thứ tự (vì các cuộc gọi async không nhất thiết phải hoàn thành theo thứ tự bắt đầu).

Nhưng nếu bạn cần trả về những kết quả đó từ một hàm thì sao? Như các câu trả lời khác đã chỉ ra, bạn không thể; bạn phải chấp nhận chức năng của mình và gọi lại (hoặc trả lại Lời hứa ). Đây là phiên bản gọi lại:

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

Thí dụ:

Hoặc đây là một phiên bản trả về Promisethay thế:

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

Tất nhiên, nếu doSomethingAsyncthông qua lỗi, chúng tôi sẽ sử dụngreject để từ chối lời hứa khi chúng tôi gặp lỗi.)

Thí dụ:

(Hoặc thay vào đó, bạn có thể tạo một trình bao bọc để doSomethingAsynctrả lại lời hứa và sau đó thực hiện các thao tác bên dưới ...)

Nếu doSomethingAsynccung cấp cho bạn một lời hứa , bạn có thể sử dụng Promise.all:

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

Nếu bạn biết rằng doSomethingAsyncsẽ bỏ qua đối số thứ hai và thứ ba, bạn có thể chuyển trực tiếp nó sang map( mapgọi lại cuộc gọi của nó với ba đối số, nhưng hầu hết mọi người chỉ sử dụng phần lớn thời gian đầu tiên):

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

Thí dụ:

Lưu ý rằng Promise.allgiải quyết lời hứa của mình bằng một loạt kết quả của tất cả các lời hứa mà bạn đưa ra khi tất cả đã được giải quyết hoặc từ chối lời hứa của mình khi lời hứa đầu tiên bạn đưa ra từ chối.

Loạt

Giả sử bạn không muốn các hoạt động song song? Nếu bạn muốn chạy từng cái một, bạn cần đợi cho mỗi thao tác hoàn thành trước khi bạn bắt đầu tiếp theo. Đây là một ví dụ về chức năng thực hiện điều đó và gọi một cuộc gọi lại với kết quả:

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

(Vì chúng tôi đang thực hiện công việc theo chuỗi, chúng tôi chỉ có thể sử dụng results.push(result)vì chúng tôi biết rằng chúng tôi sẽ không nhận được kết quả ngoài trật tự. Ở trên chúng tôi có thể đã sử dụng results[index] = result;, nhưng trong một số ví dụ sau chúng tôi không có chỉ mục sử dụng.)

Thí dụ:

(Hoặc, một lần nữa, xây dựng một trình bao bọc cho doSomethingAsyncbạn một lời hứa và thực hiện các thao tác dưới đây ...)

Nếu doSomethingAsynccung cấp cho bạn một Promise, nếu bạn có thể sử dụng cú pháp ES2017 + (có lẽ với một bộ chuyển mã như Babel ), bạn có thể sử dụng một asynchàm với for-ofawait:

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

Thí dụ:

Nếu bạn không thể sử dụng cú pháp ES2017 + (chưa), bạn có thể sử dụng một biến thể trên mẫu "Promise giảm" (điều này phức tạp hơn so với giảm Promise thông thường vì chúng tôi không chuyển kết quả từ lần này sang lần tiếp theo, nhưng thay vào đó thu thập kết quả của họ trong một mảng):

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

Thí dụ:

... ít cồng kềnh hơn với các chức năng mũi tên 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);
});

Thí dụ:


1
Bạn có thể giải thích làm thế nào if (--expecting === 0)một phần của mã làm việc? Phiên bản gọi lại của giải pháp của bạn đang hoạt động rất tốt đối với tôi, tôi chỉ không hiểu làm thế nào, với tuyên bố đó, bạn đang kiểm tra số lượng phản hồi đã hoàn thành. Đánh giá cao nó chỉ là thiếu kiến ​​thức về phía tôi. Có một cách khác mà kiểm tra có thể được viết?
Sarah

@Sarah: expectingbắt đầu với giá trị của array.length, đó là số lượng yêu cầu chúng tôi sẽ thực hiện. Chúng tôi biết cuộc gọi lại sẽ không được gọi cho đến khi tất cả các yêu cầu đó được bắt đầu. Trong cuộc gọi lại, hãy if (--expecting === 0)thực hiện điều này: 1. Giảm expecting(chúng tôi đã nhận được phản hồi, vì vậy chúng tôi mong đợi một phản hồi ít hơn) và nếu giá trị sau khi giảm là 0 (chúng tôi không mong đợi bất kỳ phản hồi nào nữa), chúng tôi sẽ làm xong!
TJ Crowder

1
@PatrickRoberts - Cảm ơn !! Có, lỗi sao chép và dán, đối số thứ hai đó đã hoàn toàn bị bỏ qua trong ví dụ đó (đó là lý do duy nhất nó không thất bại, vì như bạn đã chỉ ra, resultskhông tồn tại). :-) Đã sửa nó.
TJ Crowder

111

Hãy xem ví dụ này:

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

Như bạn có thể thấy getJoketrả lại một lời hứa đã được giải quyết (nó được giải quyết khi quay lại res.data.value). Vì vậy, bạn đợi cho đến khi yêu cầu $ http.get được hoàn thành và sau đó console.log (res.joke) được thực thi (như một luồng không đồng bộ thông thường).

Đây là plnkr:

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

Cách ES6 (không đồng bộ - đang chờ)

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

107

Đây là một trong những nơi mà hai cách liên kết dữ liệu hoặc khái niệm lưu trữ được sử dụng trong nhiều khung JavaScript mới sẽ hoạt động tốt cho bạn ...

Vì vậy, nếu bạn đang sử dụng Angular, React hoặc bất kỳ khung công tác nào khác có hai cách ràng buộc dữ liệu hoặc lưu trữ khái niệm thì vấn đề này chỉ đơn giản là khắc phục cho bạn, vì vậy, nói một cách dễ dàng, kết quả của bạn là undefinedở giai đoạn đầu tiên, vì vậy bạn đã nhận được result = undefinedtrước khi bạn nhận được dữ liệu, ngay sau khi bạn nhận được kết quả, nó sẽ được cập nhật và được gán cho giá trị mới đáp ứng cuộc gọi Ajax của bạn ...

Nhưng làm thế nào bạn có thể làm điều đó trong javascript hoặc jQuery thuần túy chẳng hạn như bạn đã hỏi trong câu hỏi này?

Bạn có thể sử dụng một cuộc gọi lại , lời hứa và gần đây có thể quan sát được để xử lý nó cho bạn, ví dụ như trong lời hứa, chúng tôi có một số chức năng như success()hoặc then()sẽ được thực thi khi dữ liệu của bạn sẵn sàng cho bạn, tương tự với chức năng gọi lại hoặc đăng ký có thể quan sát được .

Ví dụ: trong trường hợp bạn đang sử dụng jQuery , bạn có thể làm một cái gì đó như thế này:

$(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
});

Để biết thêm thông tin nghiên cứu về những lời hứaquan sát , đó là những cách mới hơn để thực hiện công cụ không đồng bộ này.


Điều này là tốt ở phạm vi toàn cầu, nhưng trong một số bối cảnh mô-đun có thể bạn muốn đảm bảo bối cảnh thích hợp cho gọi lại ví dụ$.ajax({url: "api/data", success: fooDone.bind(this)});
steve.sims

8
Điều này thực sự không chính xác vì React là ràng buộc dữ liệu một chiều
Matthew Brent

@MatthewBrent bạn không sai, nhưng cũng không đúng, đạo cụ React là đối tượng và nếu thay đổi, chúng sẽ thay đổi trong suốt ứng dụng, nhưng đó không phải là cách mà nhà phát triển React khuyên bạn nên sử dụng ...
Alireza

98

Đây là một vấn đề rất phổ biến mà chúng tôi gặp phải trong khi đấu tranh với 'bí ẩn' của JavaScript. Hãy để tôi thử làm sáng tỏ bí ẩn này ngày hôm nay.

Hãy bắt đầu với một hàm JavaScript đơn giản:

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

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

Đó là một lệnh gọi hàm đồng bộ đơn giản (trong đó mỗi dòng mã được 'hoàn thành với công việc của nó' trước chuỗi tiếp theo) và kết quả giống như mong đợi.

Bây giờ, hãy thêm một chút thay đổi, bằng cách giới thiệu một chút độ trễ trong hàm của chúng ta, để tất cả các dòng mã không được 'hoàn thành' theo trình tự. Do đó, nó sẽ mô phỏng hành vi không đồng bộ của hàm:

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

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

Vì vậy, bạn đi, sự chậm trễ đó chỉ phá vỡ chức năng chúng tôi mong đợi! Nhưng chính xác thì chuyện gì đã xảy ra? Chà, nó thực sự khá logic nếu bạn nhìn vào mã. hàm foo(), khi thực thi, không trả về giá trị nào (do đó giá trị được trả về là undefined), nhưng nó sẽ khởi động bộ đếm thời gian, thực thi một hàm sau 1 giây để trả về 'wohoo'. Nhưng như bạn có thể thấy, giá trị được gán cho thanh là thứ được trả về ngay lập tức từ foo (), không có nghĩa là chỉ undefined.

Vì vậy, làm thế nào để chúng ta giải quyết vấn đề này?

Chúng ta hãy hỏi chức năng của chúng tôi cho một KHUYẾN MÃI . Promise thực sự là về ý nghĩa của nó: nó có nghĩa là hàm đảm bảo cho bạn cung cấp bất kỳ đầu ra nào trong tương lai. vì vậy hãy xem nó hoạt động cho vấn đề nhỏ của chúng tôi ở trên:

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

Do đó, tóm tắt là - để giải quyết các chức năng không đồng bộ như các cuộc gọi dựa trên ajax, v.v., bạn có thể sử dụng lời hứa với resolvegiá trị (mà bạn dự định trả về). Do đó, trong ngắn hạn, bạn giải quyết giá trị thay vì trả về , trong các hàm không đồng bộ.

CẬP NHẬT (Hứa với async / await)

Ngoài việc sử dụng then/catchđể làm việc với những lời hứa, còn tồn tại một cách tiếp cận nữa. Ý tưởng là nhận ra một hàm không đồng bộ và sau đó chờ đợi các lời hứa được giải quyết, trước khi chuyển sang dòng mã tiếp theo. Nó vẫn chỉ là promisesdưới mui xe, nhưng với một cách tiếp cận cú pháp khác. Để làm cho mọi thứ rõ ràng hơn, bạn có thể tìm thấy một so sánh dưới đây:

sau đó / bắt phiên bản:

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

phiên bản async / await:

  async function saveUsers(){
     try{
        let users = await getUsers()
        saveSomewhere(users);
     }
     catch(err){
        console.error(err);
     }
  }

Đây có còn được coi là cách tốt nhất để trả về giá trị từ một lời hứa hoặc không đồng bộ / chờ đợi không?
edwardsmarkf

3
@edwardsmarkf Cá nhân tôi không nghĩ có một cách tốt nhất như vậy. Tôi sử dụng lời hứa với then / Catch, async / await cũng như các trình tạo cho các phần async của mã của tôi. Nó phần lớn phụ thuộc vào bối cảnh sử dụng.
Anish K.

96

Một cách tiếp cận khác để trả về một giá trị từ hàm không đồng bộ, là truyền vào một đối tượng sẽ lưu trữ kết quả từ hàm không đồng bộ.

Đây là một ví dụ tương tự:

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

Tôi đang sử dụng resultđối tượng để lưu trữ giá trị trong quá trình hoạt động không đồng bộ. Điều này cho phép kết quả có sẵn ngay cả sau khi công việc không đồng bộ.

Tôi sử dụng phương pháp này rất nhiều. Tôi sẽ quan tâm để biết cách tiếp cận này hoạt động tốt như thế nào khi kết nối lại kết quả thông qua các mô-đun liên tiếp.


9
Không có gì đặc biệt về việc sử dụng một đối tượng ở đây. Nó sẽ hoạt động tốt nếu bạn được chỉ định anh ấy trả lời trực tiếpresult . Nó hoạt động vì bạn đang đọc biến sau khi chức năng async hoàn tất.
Felix Kling

85

Mặc dù lời hứa và cuộc gọi lại hoạt động tốt trong nhiều tình huống, nhưng thật khó để thể hiện điều gì đó như:

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

Bạn cuối cùng sẽ trải qua async1; kiểm tra nếu namekhông xác định hay không và gọi lại cho phù hợp.

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

async1(name, async2)

Mặc dù nó ổn trong các ví dụ nhỏ nhưng nó gây khó chịu khi bạn có nhiều trường hợp tương tự và xử lý lỗi liên quan.

Fibers giúp giải quyết vấn đề.

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
}

Bạn có thể kiểm tra dự án ở đây .


1
@recurf - Đây không phải là dự án của tôi. Bạn có thể thử sử dụng theo dõi vấn đề của họ.
rohithpr

1
cái này có giống với hàm tạo không? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/iêu *
Emanegux

1
Điều này vẫn còn có liên quan?
Aluan Haddad 18/03/18

Bạn có thể sử dụng async-awaitnếu bạn đang sử dụng một số phiên bản nút mới nhất. Nếu ai đó bị mắc kẹt với các phiên bản cũ hơn, họ có thể sử dụng phương pháp này.
rohithpr

83

Ví dụ sau đây tôi đã viết cho thấy làm thế nào để

  • Xử lý các cuộc gọi HTTP không đồng bộ;
  • Đợi phản hồi từ mỗi lệnh gọi API;
  • Sử dụng mẫu Promise ;
  • Sử dụng mẫu Promise.all để tham gia nhiều cuộc gọi HTTP;

Ví dụ làm việc này là khép kín. Nó sẽ định nghĩa một đối tượng yêu cầu đơn giản sử dụng XMLHttpRequestđối tượng cửa sổ để thực hiện cuộc gọi. Nó sẽ xác định một chức năng đơn giản để chờ đợi một loạt các lời hứa sẽ được hoàn thành.

Bối cảnh. Ví dụ này là truy vấn điểm cuối API Spotify để tìm kiếm playlistcác đối tượng cho một chuỗi các chuỗi truy vấn nhất định:

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

Đối với mỗi mục, một Promise mới sẽ kích hoạt một khối - ExecutionBlock, phân tích kết quả, lên lịch cho một loạt các lời hứa mới dựa trên mảng kết quả, đó là danh sách các userđối tượng Spotify và thực hiện cuộc gọi HTTP mới trong phạm vi ExecutionProfileBlockkhông đồng bộ.

Sau đó, bạn có thể thấy cấu trúc Promise lồng nhau, cho phép bạn sinh ra nhiều cuộc gọi HTTP lồng nhau và hoàn toàn không đồng bộ và tham gia kết quả từ mỗi tập hợp con của các cuộc gọi thông qua Promise.all.

LƯU Ýsearch API Spotify gần đây sẽ yêu cầu mã thông báo truy cập được chỉ định trong tiêu đề yêu cầu:

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

Vì vậy, bạn để chạy ví dụ sau bạn cần đặt mã thông báo truy cập của mình vào tiêu đề yêu cầu:

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

Tôi đã thảo luận rộng rãi về giải pháp này ở đây .


80

Câu trả lời ngắn gọn là, bạn phải thực hiện một cuộc gọi lại như thế này:

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

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

78

Câu trả lời năm 2017: bây giờ bạn có thể làm chính xác những gì bạn muốn trong mọi trình duyệt và nút hiện tại

Điều này khá đơn giản:

  • Trả lại một lời hứa
  • Sử dụng 'await' , sẽ báo cho JavaScript để chờ lời hứa được giải quyết thành một giá trị (như phản hồi HTTP)
  • Thêm từ khóa 'async' vào hàm cha

Đây là phiên bản làm việc của mã của bạn:

(async function(){

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

})()

await được hỗ trợ trong tất cả các trình duyệt hiện tại và nút 8


7
Thật không may, điều này chỉ hoạt động với các hàm trả về lời hứa - ví dụ: nó không hoạt động với API Node.js, sử dụng hàm gọi lại. Và tôi không khuyên bạn nên sử dụng nó mà không có Babel, vì không phải ai cũng sử dụng "trình duyệt hiện tại".
Michał Perłakowski

2
@ MichałPerłakowski nút 8 bao gồm nodejs.org/api/util.html#util_util_promisify_origen có thể được sử dụng để thực hiện các lời hứa trả về API của node.js. Việc bạn có thời gian và tiền bạc để hỗ trợ các trình duyệt không hiện tại hay không rõ ràng tùy thuộc vào tình huống của bạn.
mikemaccana

IE 11 vẫn là một trình duyệt hiện tại vào năm 2018, thật đáng buồn và nó không hỗ trợawait/async
Juan Mendes

IE11 không phải là một trình duyệt hiện tại. Nó đã được phát hành 5 năm trước, có thị phần trên toàn thế giới là 2,5% theo caniuse, và trừ khi ai đó đang tăng gấp đôi ngân sách của bạn để bỏ qua tất cả các công nghệ hiện tại thì nó không đáng giá thời gian của mọi người.
mikemaccana

76

Js là một luồng đơn.

Trình duyệt có thể được chia thành ba phần:

1) Vòng lặp sự kiện

2) API web

3) Hàng đợi sự kiện

Vòng lặp sự kiện chạy mãi mãi, tức là loại vòng lặp vô hạn. Hàng đợi là nơi tất cả các chức năng của bạn được đẩy vào một số sự kiện (ví dụ: nhấp chuột) lần lượt được thực hiện trong hàng đợi và đưa vào vòng lặp Sự kiện thực hiện chức năng này và tự chuẩn bị nó cho lần tiếp theo sau lần đầu tiên được thực thi. Điều này có nghĩa là Thực thi một chức năng không bắt đầu cho đến khi chức năng trước khi hàng đợi được thực thi trong vòng lặp sự kiện.

Bây giờ chúng ta hãy nghĩ rằng chúng ta đã đẩy hai hàm trong một hàng đợi là để lấy dữ liệu từ máy chủ và một hàm khác sử dụng dữ liệu đó. Chúng ta đã đẩy hàm serverRequest () trong hàng đợi trước sau đó là hàm useiseData (). Hàm serverRequest đi vào vòng lặp sự kiện và thực hiện cuộc gọi đến máy chủ vì chúng tôi không bao giờ biết sẽ mất bao nhiêu thời gian để lấy dữ liệu từ máy chủ nên quá trình này được dự kiến ​​sẽ mất thời gian và vì vậy chúng tôi bận rộn với vòng lặp sự kiện của chúng tôi, do đó, đó là trang Web API đi vào vai trò, nó lấy chức năng này từ vòng lặp sự kiện và giao dịch với máy chủ làm cho vòng lặp sự kiện miễn phí để chúng ta có thể thực thi chức năng tiếp theo từ hàng đợi. Hàm tiếp theo trong hàng đợi là produciseData () đi theo vòng lặp nhưng vì không có dữ liệu nên nó đi lãng phí và thực thi chức năng tiếp theo tiếp tục cho đến khi kết thúc hàng đợi (Đây được gọi là cuộc gọi Async tức là chúng ta có thể làm gì đó khác cho đến khi nhận được dữ liệu)

Giả sử hàm serverRequest () của chúng ta có câu lệnh return trong mã, khi chúng ta lấy lại dữ liệu từ API Web của máy chủ sẽ đẩy nó vào hàng đợi ở cuối hàng đợi. Khi nó được đẩy vào cuối hàng đợi, chúng tôi không thể sử dụng dữ liệu của nó vì không còn chức năng nào trong hàng đợi để sử dụng dữ liệu này. Do đó, không thể trả lại một cái gì đó từ Async Call.

Do đó, Giải pháp cho vấn đề này là gọi lại hoặc hứa .

Hình ảnh từ một trong những câu trả lời ở đây, Giải thích chính xác việc sử dụng gọi lại ... Chúng tôi cung cấp chức năng của chúng tôi (chức năng sử dụng dữ liệu được trả về từ máy chủ) cho chức năng gọi máy chủ.

Gọi lại

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

}

Trong mã của tôi, nó được gọi là

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

Gọi lại Javscript.info


68

Bạn có thể sử dụng thư viện tùy chỉnh này (được viết bằng Promise) để thực hiện cuộc gọi từ xa.

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

Ví dụ sử dụng đơn giản:

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

67

Một giải pháp khác là thực thi mã thông qua trình thực thi tuần tự nsynjs .

Nếu chức năng cơ bản được hứa hẹn

nsynjs sẽ đánh giá tất cả các lời hứa một cách tuần tự và đưa kết quả lời hứa vào datatài sản:

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>

Nếu chức năng cơ bản không được hứa hẹn

Bước 1. Gói chức năng với hàm gọi lại vào trình bao bọc nhận biết nsynjs (nếu nó có phiên bản được quảng cáo, bạn có thể bỏ qua bước này):

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;

Bước 2. Đặt logic đồng bộ vào chức năng:

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

Bước 3. Chạy chức năng đồng bộ qua nsynjs:

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

Nsynjs sẽ đánh giá tất cả các toán tử và các biểu thức từng bước, tạm dừng thực thi trong trường hợp nếu kết quả của một số chức năng chậm chưa sẵn sàng.

Thêm ví dụ tại đây: https://github.com/amaksr/nsynjs/tree/master/examples


2
Hay đấy. Tôi thích cách nó cho phép mã hóa async gọi theo cách bạn thực hiện bằng các ngôn ngữ khác. Nhưng về mặt kỹ thuật nó không phải là JavaScript thực?
J Morris

41

ECMAScript 6 có 'bộ tạo' cho phép bạn dễ dàng lập trình theo kiểu không đồng bộ.

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

Để chạy mã trên, bạn làm điều này:

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

Nếu bạn cần nhắm mục tiêu các trình duyệt không hỗ trợ ES6, bạn có thể chạy mã thông qua Babel hoặc trình biên dịch đóng để tạo ECMAScript 5.

Cuộc gọi lại ...argsđược gói trong một mảng và bị phá hủy khi bạn đọc chúng để mẫu có thể đối phó với các cuộc gọi lại có nhiều đối số. Ví dụ với nút fs :

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

39

Dưới đây là một số cách tiếp cận để làm việc với các yêu cầu không đồng bộ:

  1. Đối tượng Promise trình duyệt
  2. Q - Một thư viện hứa cho JavaScript
  3. A + Promising.js
  4. jQuery hoãn lại
  5. API XMLHttpRequest
  6. Sử dụng khái niệm gọi lại - Khi thực hiện trong câu trả lời đầu tiên

Ví dụ: jQuery trì hoãn triển khai để làm việc với nhiều yêu cầu

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

Chúng ta thấy mình trong một vũ trụ dường như tiến lên theo chiều mà chúng ta gọi là "thời gian". Chúng tôi không thực sự hiểu thời gian là gì, nhưng chúng tôi đã phát triển trừu tượng và từ vựng cho phép chúng tôi suy luận và nói về nó: "quá khứ", "hiện tại", "tương lai", "trước", "sau".

Các hệ thống máy tính chúng tôi xây dựng - ngày càng nhiều hơn - có thời gian là một khía cạnh quan trọng. Một số điều được thiết lập để xảy ra trong tương lai. Sau đó, những điều khác cần phải xảy ra sau khi những điều đầu tiên cuối cùng xảy ra. Đây là khái niệm cơ bản được gọi là "không điển hình". Trong thế giới ngày càng kết nối của chúng ta, trường hợp không điển hình phổ biến nhất đang chờ một hệ thống từ xa đáp ứng một số yêu cầu.

Hãy xem xét một ví dụ. Bạn gọi cho milkman và gọi một ít sữa. Khi nó đến, bạn muốn đặt nó vào cà phê của bạn. Bạn không thể đặt sữa vào cà phê của bạn ngay bây giờ, vì nó chưa ở đây. Bạn phải đợi nó đến trước khi đưa nó vào cà phê của bạn. Nói cách khác, những điều sau đây sẽ không hoạt động:

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

Bởi vì JS không có cách nào để biết rằng nó cần phải chờ đợi cho order_milkđến khi kết thúc trước khi nó thực thi put_in_coffee. Nói cách khác, nó không biết đó order_milkkhông đồng bộ thứ sẽ không dẫn đến sữa cho đến một thời điểm nào đó trong tương lai. JS và các ngôn ngữ khai báo khác thực thi một câu lệnh khác mà không cần chờ đợi.

Cách tiếp cận JS cổ điển cho vấn đề này, lợi dụng thực tế là JS hỗ trợ các hàm như các đối tượng hạng nhất có thể được truyền qua, là chuyển một hàm làm tham số cho yêu cầu không đồng bộ, sau đó nó sẽ gọi khi hoàn thành nhiệm vụ của nó đôi khi trong tương lai. Đó là cách tiếp cận "gọi lại". Nó trông như thế này:

order_milk(put_in_coffee);

order_milk khởi động, ra lệnh cho sữa, sau đó, khi và chỉ khi nó đến, nó gọi put_in_coffee .

Vấn đề với phương pháp gọi lại này là nó gây ô nhiễm ngữ nghĩa thông thường của một hàm báo cáo kết quả của nó với return; thay vào đó, các hàm không được báo cáo kết quả của chúng bằng cách gọi một cuộc gọi lại được đưa ra dưới dạng tham số. Ngoài ra, cách tiếp cận này có thể nhanh chóng trở nên khó sử dụng khi xử lý các chuỗi sự kiện dài hơn. Ví dụ: giả sử tôi muốn đợi sữa được đưa vào cà phê, và sau đó và chỉ sau đó thực hiện bước thứ ba, cụ thể là uống cà phê. Tôi cuối cùng cần phải viết một cái gì đó như thế này:

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

nơi tôi chuyển đến put_in_coffeecả sữa để đặt vào đó, và cả hành động (drink_coffee ) để thực thi khi sữa đã được đưa vào. Mã như vậy trở nên khó viết, đọc và gỡ lỗi.

Trong trường hợp này, chúng tôi có thể viết lại mã trong câu hỏi dưới dạng:

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

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

Nhập lời hứa

Đây là động lực cho khái niệm "lời hứa", một loại giá trị cụ thể đại diện cho một kết quả tương lai hoặc không đồng bộ của một loại nào đó. Nó có thể đại diện cho điều gì đó đã xảy ra, hoặc điều đó sẽ xảy ra trong tương lai, hoặc có thể không bao giờ xảy ra. Lời hứa có một phương thức duy nhất, được đặt tên then, mà bạn vượt qua một hành động sẽ được thực hiện khi kết quả mà lời hứa đại diện đã được thực hiện.

Trong trường hợp sữa và cà phê của chúng tôi, chúng tôi thiết kế order_milkđể trả lại lời hứa cho sữa đến, sau đó xác định put_in_coffeelà một thenhành động, như sau:

order_milk() . then(put_in_coffee)

Một lợi thế của điều này là chúng ta có thể xâu chuỗi những thứ này lại với nhau để tạo ra chuỗi các lần xuất hiện trong tương lai ("chuỗi"):

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

Hãy áp dụng lời hứa cho vấn đề cụ thể của bạn. Chúng tôi sẽ gói logic yêu cầu của chúng tôi bên trong một hàm, trả về một lời hứa:

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

Trên thực tế, tất cả những gì chúng tôi đã làm được thêm vào một returncuộc gọi đến $.ajax. Điều này hoạt động vì jQuery $.ajaxđã trả về một loại điều giống như lời hứa. (Trong thực tế, không đi sâu vào chi tiết, chúng tôi muốn thực hiện cuộc gọi này để trả lại lời hứa thực sự hoặc sử dụng một số thay thế cho $.ajaxviệc đó.) Bây giờ, nếu chúng tôi muốn tải tệp và đợi nó kết thúc và sau đó làm một cái gì đó, chúng ta có thể nói một cách đơn giản

get_data() . then(do_something)

ví dụ,

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

Khi sử dụng lời hứa, chúng tôi sẽ chuyển nhiều chức năng vào then, vì vậy việc sử dụng các hàm mũi tên kiểu ES6 nhỏ gọn hơn thường rất hữu ích:

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

Các async từ khóa

Nhưng vẫn còn một điều gì đó không hài lòng về việc phải viết mã một chiều nếu đồng bộ và một cách hoàn toàn khác nếu không đồng bộ. Để đồng bộ, chúng tôi viết

a();
b();

nhưng nếu akhông đồng bộ, với những lời hứa chúng ta phải viết

a() . then(b);

Ở trên, chúng tôi đã nói, "JS không có cách nào để biết rằng nó cần đợi cuộc gọi đầu tiên kết thúc trước khi nó thực hiện cuộc gọi thứ hai". Nó sẽ không được tốt đẹp nếu có một số cách để nói với JS đó? Nó chỉ ra rằng có - awaittừ khóa, được sử dụng bên trong một loại chức năng đặc biệt gọi là chức năng "không đồng bộ". Tính năng này là một phần của phiên bản ES sắp ra mắt nhưng đã có sẵn trong các bộ chuyển đổi, chẳng hạn như Babel được cài đặt sẵn. Điều này cho phép chúng ta chỉ cần viết

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

Trong trường hợp của bạn, bạn sẽ có thể viết một cái gì đó như

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

37

Câu trả lời ngắn : foo()Phương thức của bạn trả về ngay lập tức, trong khi $ajax()cuộc gọi thực hiện không đồng bộ sau khi hàm trả về . Vấn đề là sau đó làm thế nào hoặc ở đâu để lưu trữ các kết quả được truy xuất bằng lệnh gọi async sau khi nó trả về.

Một số giải pháp đã được đưa ra trong chủ đề này. Có lẽ cách dễ nhất là truyền một đối tượng cho foo()phương thức và lưu trữ kết quả trong một thành viên của đối tượng đó sau khi cuộc gọi async hoàn thành.

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

Lưu ý rằng cuộc gọi đến foo()vẫn sẽ không có gì hữu ích. Tuy nhiên, kết quả của cuộc gọi async bây giờ sẽ được lưu trữ trong result.response.


14
Trong khi điều này hoạt động, nó không thực sự tốt hơn việc gán cho một biến toàn cục.
Felix Kling

36

Sử dụng một callback()chức năng bên trong foo()thành công. Hãy thử theo cách này. Nó là đơn giản và dễ hiểu.  

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();

29

Câu hỏi là:

Làm cách nào để trả về phản hồi từ cuộc gọi không đồng bộ?

mà CÓ THỂ được hiểu là:

Làm thế nào để làm cho mã không đồng bộ trông đồng bộ ?

Giải pháp sẽ là tránh các cuộc gọi lại và sử dụng kết hợp Promiseasync / await .

Tôi muốn đưa ra một ví dụ cho một yêu cầu Ajax.

(Mặc dù nó có thể được viết bằng Javascript, nhưng tôi thích viết nó bằng Python và biên dịch nó thành Javascript bằng Transcrypt . Nó sẽ đủ rõ ràng.)

Trước tiên hãy cho phép sử dụng JQuery, để có $sẵn dưới dạng S:

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

Xác định hàm trả về một Promise , trong trường hợp này là lệnh gọi 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()

Sử dụng mã không đồng bộ như thể nó là đồng bộ :

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

29

Sử dụng lời hứa

Câu trả lời hoàn hảo nhất cho câu hỏi này là sử dụng 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);
  });
}

Sử dụng

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

Nhưng đợi đã ...!

Có một vấn đề với việc sử dụng lời hứa!

Tại sao chúng ta nên sử dụng Promise tùy chỉnh của riêng mình?

Tôi đã sử dụng giải pháp này một thời gian cho đến khi tôi phát hiện ra có lỗi trong các trình duyệt cũ:

Uncaught ReferenceError: Promise is not defined

Vì vậy, tôi quyết định triển khai lớp Promise của riêng mình cho ES3 để bên dưới trình biên dịch js nếu nó không được xác định. Chỉ cần thêm mã này trước mã chính của bạn và sau đó sử dụng Promise một cách an toàn!

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

28

Tất nhiên có nhiều cách tiếp cận như yêu cầu đồng bộ, hứa hẹn, nhưng từ kinh nghiệm của tôi, tôi nghĩ bạn nên sử dụng phương pháp gọi lại. Đó là hành vi tự nhiên không đồng bộ của Javascript. Vì vậy, đoạn mã của bạn có thể được viết lại một chút khác nhau:

function foo() {
    var result;

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

    return result;
}

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

5
Không có gì không đồng bộ về các cuộc gọi lại hoặc JavaScript.
Aluan Haddad 18/03/18

19

Thay vì ném mã vào bạn, có 2 khái niệm là chìa khóa để hiểu cách JS xử lý các cuộc gọi lại và tính không đồng bộ. (Ngay cả là một từ?)

Mô hình sự kiện và vòng lặp đồng thời

Có ba điều bạn cần lưu ý; Việc xếp hàng; vòng lặp sự kiện và ngăn xếp

Theo nghĩa rộng, đơn giản, vòng lặp sự kiện giống như người quản lý dự án, nó liên tục lắng nghe bất kỳ chức năng nào muốn chạy và giao tiếp giữa hàng đợi và ngăn xếp.

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

Một khi nó nhận được một tin nhắn để chạy một cái gì đó, nó sẽ thêm nó vào hàng đợi. Hàng đợi là danh sách những thứ đang chờ để thực thi (như yêu cầu AJAX của bạn). hãy tưởng tượng nó như thế này:

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

Khi một trong những thông báo này sẽ thực thi, nó sẽ bật thông báo từ hàng đợi và tạo một ngăn xếp, ngăn xếp là tất cả mọi thứ mà JS cần thực hiện để thực hiện hướng dẫn trong thông báo. Vì vậy, trong ví dụ của chúng tôi, nó được yêu cầu gọifoobarFunc

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

Vì vậy, bất cứ điều gì mà foobarFunc cần thực hiện (trong trường hợp của chúng tôi anotherFunction) sẽ được đẩy lên ngăn xếp. được thực thi và sau đó quên về - vòng lặp sự kiện sẽ chuyển sang điều tiếp theo trong hàng đợi (hoặc nghe tin nhắn)

Điều quan trọng ở đây là thứ tự thực hiện. Đó là

KHI NÀO là một cái gì đó sẽ chạy

Khi bạn thực hiện cuộc gọi bằng AJAX cho bên ngoài hoặc chạy bất kỳ mã không đồng bộ nào (ví dụ như setTimeout), Javascript sẽ phụ thuộc vào phản hồi trước khi có thể tiến hành.

Câu hỏi lớn là khi nào nó sẽ nhận được phản hồi? Câu trả lời là chúng tôi không biết - vì vậy vòng lặp sự kiện đang chờ thông báo đó nói "hey run me". Nếu JS chỉ chờ xung quanh thông báo đó một cách đồng bộ thì ứng dụng của bạn sẽ đóng băng và nó sẽ bị hút. Vì vậy, JS tiếp tục thực hiện mục tiếp theo trong hàng đợi trong khi chờ thông báo được thêm lại vào hàng đợi.

Đó là lý do tại sao với chức năng không đồng bộ, chúng tôi sử dụng những thứ gọi là gọi lại . Nó giống như một lời hứa hoàn toàn theo nghĩa đen. Như tôi hứa sẽ trả lại một cái gì đó vào một lúc nào đó jQuery sử dụng các cuộc gọi lại cụ thể được gọi deffered.done deffered.faildeffered.always(trong số những thứ khác). Bạn có thể thấy tất cả ở đây

Vì vậy, những gì bạn cần làm là vượt qua một chức năng được hứa hẹn sẽ thực thi tại một số điểm với dữ liệu được truyền cho nó.

Bởi vì một cuộc gọi lại không được thực thi ngay lập tức, nhưng sau đó, điều quan trọng là chuyển tham chiếu đến hàm không được thực thi. vì thế

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

vì vậy hầu hết thời gian (nhưng không phải luôn luôn) bạn sẽ fookhông vượt quafoo()

Hy vọng rằng sẽ có ý nghĩa. Khi bạn gặp phải những điều như thế này có vẻ khó hiểu - tôi khuyên bạn nên đọc tài liệu đầy đủ để ít nhất hiểu được về nó. Nó sẽ làm cho bạn một nhà phát triển tốt hơn nhiều.


18

Sử dụng ES2017, bạn nên có cái này như là khai báo hàm

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

Và thực hiện nó như thế này.

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

Hoặc cú pháp Promise

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

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

})

chức năng thứ hai có thể tái sử dụng không ??
Zum Dummi

Làm thế nào để bạn sử dụng kết quả nếu oncolse, log được gọi? Không phải mọi thứ chỉ đi đến bàn điều khiển vào thời điểm đó sao?
Ken Ingram
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.