a

Câu hỏi Làm cách nào để trả lại phản hồi từ một cuộc gọi không đồng bộ?


Tôi có một chức năng foo tạo một yêu cầu Ajax. Làm cách nào tôi có thể trả lời phản hồi từ foo?

Tôi đã thử trả lại giá trị từ success gọi lại cũng như gán phản hồi cho biến cục bộ bên trong hàm và trả về biến đó, nhưng không cách nào trong số đó 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`.

4321
2018-01-08 17:06


gốc


@MaximShoustin Tôi đã thêm một câu trả lời chỉ JavaScript (không có jQuery) nhưng như Felix nói - "Làm thế nào để tôi trả lời phản hồi từ một cuộc gọi AJAX" thực sự là một cách hay để nói "Làm thế nào để đồng thời làm việc trong JavaScript". Chưa kể đến Angular's $http rất giống với jQuery $.ajax anyway với một số khác biệt nhỏ (như interoping với chu kỳ tiêu hóa). - Benjamin Gruenbaum
Đừng sử dụng async: false. Nó sẽ đóng băng trình duyệt và hướng người dùng đi. Đọc blog.slaks.net/2015-01-04/async-method-patterns để tìm hiểu cách làm việc với các hoạt động không đồng bộ. - SLaks
Có thể như bạn đã làm điều này bằng cách ading async: false làm thuộc tính của tham số đầu tiên. Xem câu trả lời của tôi dưới đây. @tvanfosson đã đưa ra giải pháp này trên Stackoverflow. - arrowman
mẹ này giúp bạn codingbin.com/get-return-data-ajax-call - Manoj Kumar
@SLaks Một người nào đó vui lòng chuyển tiếp đến trang web "các nhà phát triển ngôi sao nhạc rock" tại AirAsia.com. Đó là một nghiên cứu điển hình tuyệt vời (được tăng cường bởi các cuộc gọi ajax mất vài giây) vì sao không bao giờ sử dụng async: false. - Mâtt Frëëman


Các câu trả lời:


-> Để có 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 một hàm? - Tham chiếu mã không đồng bộ 

-> Nếu bạn đã hiểu sự cố, hãy bỏ qua các giải pháp có thể có bên dưới.

Vấn đề

Các A trong Ajax viết tắt của không đồng bộ . Điều đó có nghĩa là việc gửi yêu cầu (hoặc đúng hơn là nhận được phản hồi) được lấy ra khỏi luồng thực hiện bình thường. Trong ví dụ của bạn, $.ajax trả về ngay lập tức và câu lệnh tiếp theo, return result;, được thực thi trước hàm bạn đã chuyển success gọi lại thậm chí còn đượ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 lưu lượng đồng bộ và không đồng bộ rõ ràng hơn:

Đồng bộ

Hãy tưởng tượng bạn thực hiện một cuộc gọi điện thoại cho một người bạn và yêu cầu anh ta tìm một cái 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 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 cuộc gọi hàm có 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 du findItem có thể mất nhiều thời gian để thực thi, bất kỳ mã nào đến sau var item = findItem(); phải chờ đợi cho đến khi hàm trả về kết quả.

Không đồng bộ

Bạn gọi cho bạn của bạn một lần nữa 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àng 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 gác máy, rời khỏi nhà và làm bất cứ điều gì bạn đị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 mà anh ấy đã cung cấp cho bạn.

Đó là chính xác những gì đang 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 hiện tiếp tục ngay lập tức và câu lệnh sau khi cuộc gọi Ajax được thực hiện. Để nhận được phản hồi cuối cùng, bạn cung cấp một hàm để được gọi khi nhận được phản hồi, gọi lại (chú ý một cái gì đó? gọi lại ?). Bất kỳ câu lệnh nào đến sau khi cuộc gọi đó được thực thi trước khi gọi lại.


Các giải pháp)

Chấp nhận bản chất không đồng bộ của JavaScript! Trong khi một số hoạt động không đồng bộ nhất định cung cấp các đối tác đồng bộ (vì vậy "Ajax"), thường không khuyến khích sử dụng chúng, đặc biệt là trong ngữ cảnh trình duyệt.

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

JavaScript chạy trong chuỗi giao diện người dùng của trình duyệt và bất kỳ quá trình chạy dài nào sẽ khóa giao diện người dùng, làm cho nó không phản hồi. Ngoài ra, có giới hạn trên về thời gian thực thi cho JavaScript và trình duyệt sẽ yêu cầu người dùng có tiếp tục thực hiện hay không.

Tất cả điều này thực sự là trải nghiệm người dùng tồi. 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 ứng sẽ tồi tệ hơn cho người dùng có kết nối chậm.

Trong phần tiếp theo, chúng ta sẽ xem xét ba giải pháp khác nhau được xây dựng trên đầu trang của mỗi khác:

  • Hứa hẹn với async/await (ES2017 +, có sẵn trong các trình duyệt cũ hơn nếu bạn sử dụng trình thu thập thông tin hoặc bộ tạo lại)
  • Gọi lại (phổ biến trong nút)
  • Hứa hẹn với then() (ES2015 +, có sẵn 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 lời hứa)

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 mới được phát hành vào năm 2017 được giới thiệu hỗ trợ mức cú pháp cho các hàm không đồng bộ. Với sự giúp đỡ của async và await, bạn có thể viết không đồng bộ trong "kiểu đồng bộ". Mặc dù vậy, đừng nhầm lẫn: Mã vẫn không đồng bộ, nhưng dễ đọc / dễ hiểu hơn.

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

Quan trọng: Bạn chỉ có thể sử dụng await bên trong một async chức năng. Điều đó có nghĩa là ở cấp độ cao nhất, bạn vẫn phải làm việc trực tiếp với lời hứa.

Bạn có thể đọc thêm về async và await trên MDN.

Dưới đây là ví dụ được xây dựng dựa trên độ trễ hàng đầu ở 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 a second (just for the sake of this example)
    await delay(1000);
    // GET information about each book
    return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
  } catch(error) {
    // If any of the awaited promises was rejected, this catch block
    // would catch the rejection reason
    return null;
  }
}

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

Mới hơn trình duyệt và nút hỗ trợ phiên bản 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 bạn thành ES5 với sự trợ giúp của máy tái tạo (hoặc các công cụ sử dụng bộ tạo lại, chẳng hạn như Babel).


Cho phép hàm chấp nhận callbacks

Một cuộc gọi lại chỉ đơn giản là một hàm được truyền cho một hàm khác. Hàm kia có thể gọi hàm được truyền bất cứ khi nào nó sẵn sàng. Trong bối cảnh của một quá trình không đồng bộ, 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ụ về câu hỏi, bạn có thể thực hiện foo chấp nhận gọi lại và sử dụng nó như success gọi lại. Vì vậy, điều nà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 "inline" nhưng bạn có thể chuyển bất kỳ tham chiếu hàm 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
    });
}

callback sẽ đề cập đến hàm mà chúng ta chuyển đến foo khi chúng ta gọi nó và chúng ta chỉ cần chuyển nó vào success. I E. khi yêu cầu Ajax thành công, $.ajax sẽ gọi callback và chuyển trả lờ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ó tới cuộc gọi lại:

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

Nó dễ dàng hơn để viết mã bằng cách sử dụng callbacks hơn nó có vẻ. Xét cho cùng, JavaScript trong trình duyệt có nhiều sự kiện (sự kiện DOM). Việc nhận được phản hồi Ajax không là gì 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 bằng cách chỉ suy nghĩ qua luồng ứng dụng.


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

Các API hứa hẹn là một tính năng mới của ECMAScript 6 (ES2015), nhưng nó 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 Promises chuẩn và cung cấp các phương thức bổ sung để giảm bớt việc sử dụng và thành phần của các hàm không đồng bộ (ví dụ: bluebird).

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

Lợi thế của các callback đơn giản là chúng cho phép bạn tách mã của bạn và dễ dàng hơn để soạn thảo.

Đây là một ví dụ đơn giản về việc sử dụng 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 cuộc 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ư sau:

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ả những lợi thế hứa hẹn 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 trừu tượng tuyệt vời và 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 ý phụ: Đối tượng trì hoãn của jQuery

Đối tượng trì hoãn 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). Chúng hoạt động gần giống như lời hứa nhưng cho thấy một API hơi khác một chút.

Mỗi phương thức Ajax của jQuery đã trả về một "đối tượng trì hoãn" (thực ra là một lời hứa của một đối tượng 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ụ: Gotchas hứa hẹn

Hãy nhớ rằng những lời hứa và các đối tượng trì hoãn chỉ là hộp đựng cho một giá trị tương lai, chúng không phải là giá trị của chính nó. 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 biệt, $.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 một yêu cầu đến máy chủ và trong khi nó đợi, ngay lập tức trả về một đối tượng jQuery Ajax Deferred, không trả lời từ máy chủ. Điều đó có nghĩa là if tuyên bố sẽ luôn nhận được đối tượng Trì hoãn này, coi nó là truevà tiến hành như thể người dùng đã đăng nhập. Không tốt.

Nhưng sửa chữa rất dễ dàng:

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 khuyến nghị: 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 của họ, nhưng vì lợi ích đầy đủ, đâ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 XMLHTTPRequest đối tượng, vượt qua false làm đối số thứ ba cho .open.

jQuery

Nếu bạn dùng jQuery, bạn có thể đặt async tùy chọn để false. Lưu ý rằng tùy chọn này là không dùng nữa kể từ jQuery 1.8. Bạn có thể vẫn sử dụng success gọi lại hoặc truy cập responseText tài sản 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, $.getJSON, v.v., bạn phải thay đổi nó thành $.ajax(vì bạn chỉ có thể chuyển các tham số cấu hình cho $.ajax).

Đứng lên! Không thể tạo đồng bộ JSONP yêu cầu. JSONP bởi bản chất của nó luôn luôn là không đồng bộ (một lý do nữa để thậm chí không xem xét tùy chọn này).


4666
2018-01-08 17:06



@Pommy: Nếu bạn muốn sử dụng jQuery, bạn phải bao gồm nó. Vui lòng tham khảo trước docs.jquery.com/Tutorials:Getting_Started_with_jQuery. - Felix Kling
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 chút mỉa mai trong trường hợp này) - gibberish
@ gibberish: Mmmh, tôi không biết làm thế nào nó có thể được làm rõ hơn. Bạn có thấy cách foo được gọi và một hàm được truyền cho nó (foo(function(result) {....});)? result được sử dụng bên trong hàm này và là đáp ứng của yêu cầu Ajax. Để tham chiếu đến hàm này, tham số đầu tiên của foo được gọi là callback và giao cho success thay vì một hàm ẩn danh. Vì thế, $.ajax sẽ gọi callback khi yêu cầu thành công. Tôi cố giải thích thêm một chút. - Felix Kling
Trò chuyện cho câu hỏi này đã chết nên tôi không chắc chắn nên đề xuất các thay đổi được nêu ở đâ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ị phương thức Trì hoãn linh hoạt hơn, mà tôi nghĩ cũng có thể dễ dàng hơn một chút để làm theo cho những người học Javascript. - Chris Moschini
@Jessi: Tôi nghĩ rằng bạn hiểu lầm rằng một phần của câu trả lời. Bạn không thể sử dụng $.getJSON nế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 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


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

Mã của bạn nên 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 một công việc tốt bằng văn bản mộ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 thay thế cho những người không.

(Lưu ý, đối với những người sử dụng tính năng mới fetch API, Góc cạnh 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 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ày, hãy đọc điều đó.

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

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

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

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

(Vĩ cầm)

Giá trị của a trả lại là undefined kể từ khi a=5 một phần vẫn chưa được thực hiện. AJAX hoạt động như thế này, bạn sẽ 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 có thể cho vấn đề này là viết mã tá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 thành.

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

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

Cái này được gọi là CPS. Về cơ bản, chúng tôi đang đi qua getFive một hành động để thực hiện khi nó hoàn thành, chúng tôi đang nói cho mã của chúng tôi 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à thời gian chờ).

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

getFive(onComplete);

Mà nên cảnh báo "5" vào màn hình. (Vĩ cầm).

Phương pháp khả thi

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

  1. Thực hiện cuộc gọi AJAX đồng bộ (cho phép gọi nó là SJAX).
  2. Tái cơ cấu 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 thế! Câu trả lời của Felix đặt ra một số lý lẽ 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ả về phản hồi và tạo trải nghiệm người dùng rất xấu. Dưới đâ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ộ. Nói chung, tuy nhiên, các yêu cầu không đồng bộ nên được ưu tiên cho các yêu cầu đồng bộ vì lý do hiệu suất.

Trong ngắn hạn, 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 các vấn đề nghiêm trọng ...

nếu bạn  để làm điều đó, bạn có thể vượt qua một lá cờ: Dưới đây là cách thực hiện:

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 mã ví dụ foo có thể được thực hiện để chấp nhận gọi lại. Chúng tôi sẽ nói mã của chúng tôi cách phản ứng khi nào foo hoà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 đã chuyể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ư sau:

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

Để biết thêm chi tiết về cách thức 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 định nghĩa foo để hành động tương ứng

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ờ, hàm foo của chúng ta đã chấp nhận một hành động để chạy khi AJAX hoàn tất thành công, chúng ta có thể mở rộng thêm 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 trình xử lý không thành công). 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 đọc hướng dẫn bắt đầu sử dụng AJAX tại MDN.


888
2018-05-29 23:30



"các yêu cầu đồng bộ chặn việc thực thi mã và có thể làm rò rỉ bộ nhớ và các sự kiện" làm thế nào một yêu cầu đồng bộ có thể bị rò rỉ bộ nhớ? - Matthew G
@MatthewG Tôi đã thêm tiền thưởng vào nó câu hỏi này, Tôi sẽ xem những gì tôi có thể câu cá. Tôi đang xóa báo giá khỏi câu trả lời trong thời gian đó. - Benjamin Gruenbaum
Chỉ để tham khảo, XHR 2 cho phép chúng ta sử dụng onload xử lý, mà chỉ cháy khi readyState Là 4. Tất nhiên, nó không được hỗ trợ trong IE8. (iirc, có thể cần xác nhận.) - Florian Margaine
Lời giải thích của bạn về cách chuyển một hàm ẩn danh dưới dạng gọi lại là hợp lệ nhưng gây hiểu lầm. Ví dụ var bar = foo (); đang yêu cầu một biến được định nghĩa, trong khi foo được đề xuất của bạn (functim () {}); không xác định thanh - Robbie Averill
@BenjaminGruenbaum Làm cách nào để tôi trả lại nội dung của result trong một biến mà tôi có thể sử dụng ở một nơi khác trong mã của tôi thay vì in nó với html? - MorganFR


XMLHttpRequest 2 (trước hết là đọ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 đoạn ngắn XMLHttpRequest 2 đẹp mà hoạt động trên các trình duyệt hiện đại và cũng trên các trình duyệt di động, tôi đề nghị 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 hàm khác được liệt kê.
  2. Cuộc gọi lại được đặt trực tiếp (do đó không cần đóng thêm 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 readystate &&)
  4. Có một số tình huống khác mà tôi không nhớ rằng làm cho XMLHttpRequest 1 gây phiền nhiễu.

Có hai cách để nhận được phản hồi của cuộc gọi Ajax này (ba cách sử dụng tên biến 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 đến một lớp:

e.target.response

Thí dụ:

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

Hoặc (ở trên là chức năng ẩn danh tốt hơn luôn là 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í cả 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ợ trên tất cả các trình duyệt hiện đại. Và tôi có thể xác nhận là tôi đang sử dụng phương pháp này vì XMLHttpRequest 2 tồn tại. Tôi chưa bao giờ gặp phải bất kỳ 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 lấy các tiêu đề ở trạng thái 2.

Sử dụng XMLHttpRequest tên biến là một lỗi lớn khác khi bạn cần thực thi cuộc gọi lại bên trong các đóng cửa onload / oreadystatechange nếu không bạn sẽ bị mất.


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 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ó nhận được và đăng bài.

Ví dụ về cách 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 chuyển một phần tử biểu mẫu đầ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ộ ... đó là một điều xấu.

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


Như đã đề cập trong nhận xét, việc sử dụng lỗi && đồng bộ hoàn toàn phá vỡ điểm của câu trả lời. Cách nào ngắn gọn để sử dụng Ajax theo đúng cách?

Trình 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 kịch bản trên, bạn có một trình xử lý lỗi được định nghĩa tĩnh để nó không làm tổn hại đế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, chỉ có là viết một URL sai, trong trường hợp đó mọi trình duyệt đều gửi 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 responseType thành bộ đệm mảng blob hoặc bất kỳ thứ gì ....

Ngay cả khi bạn chuyển 'POSTAPAPAP' làm phương thức, nó sẽ không gây ra lỗi.

Ngay cả khi bạn chuyển 'fdggdgilfdghfldj' thành formdata, nó sẽ không gây ra lỗi.

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

Trong trường hợp thứ hai, nó chỉ 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 tự động phát lỗi.

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

Chỉ có this.type đượ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ả về bên trong hàm này trong hàm 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: Khi thử nghiệm đầu tiên tôi đã viết x ('x', displayAjax) ..., và nó hoàn toàn có một phản ứng ... ??? 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 S W TÌM HIỂU CNTT. Tôi đã muốn


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

Đừng làm thế.

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

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 một cách không đồng bộ. (Yeah, với vòng lặp setTimeout ... nhưng nghiêm túc?)

Một điểm khác là ... nếu bạn làm việc với các API hoặc chỉ bạn sở hữu các tệp của danh sách hoặc bất cứ điều gì bạn luôn sử dụng các hàm 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 EXTEND chức năng ...

Có, 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, với GET chỉ ...

Nhưng bạn có thể thực hiện rất nhiều thứ với XMLHttpRequest 2:

Tôi đã thực hiện một trình quản lý tải xuống (sử dụng các phạm vi ở cả hai mặt với sơ yếu lý lịch, filereader, filesystem), các trình biến đổi hình ảnh khác nhau bằng canvas, điền cơ sở dữ liệu websql với base64images 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 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 ...

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


304
2017-08-19 08:06



Trong khi câu trả lời này là tốt đẹp (Và tất cả chúng ta yêu XHR2 và đăng dữ liệu tập tin 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 đặt nó vào một bài đăng trên blog (tôi muốn nó) hoặc thậm chí trong thư viện (không chắc chắn về Tên x, ajax hoặc là xhr có thể đẹp hơn :)). Tôi không thấy nó trả về phản hồi từ cuộc gọi AJAX như thế nào. (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;)). Trên một mặt lưu ý - sẽ rất tuyệt nếu bạn quay trở lại c từ phương thức để người dùng có thể tiếp tục error v.v. - Benjamin Gruenbaum
2.ajax is meant to be async.. so NO var res=x('url').. Đó là toàn bộ vấn đề và câu trả lời này :) - Benjamin Gruenbaum
tại sao có một tham số 'c' trong các hàm, nếu trên dòng đầu tiên bạn đang ghi đè lên bất kỳ giá trị nào nó có? tui bỏ lỡ điều gì vậy? - Brian H.
Bạn có thể sử dụng tham số làm trình giữ chỗ để tránh viết nhiều lần "var" - cocco
@cocco Vì vậy, bạn đã viết mã gây hiểu lầm, không đọc được trong SO câu trả lời để tiết kiệm một vài tổ hợp phím? Làm ơn đừng làm thế. - stone


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 (với hoãn lại), thay thế XHR gốc (fetch), lưu trữ của EmberJS, BackboneJS hoặc bất kỳ thư viện nút nào trả về các lời hứa.

Mã của bạn nên 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 một công việc tốt bằng văn bản một câu trả lời cho những người sử dụng jQuery với callbacks cho AJAX. Tôi có một câu trả lời cho XHR bản địa. Câu trả lời này là để sử dụng chung các lời hứa hoặc trên giao diện người dùng 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ủ với NodeJS / io.js là không đồng bộ và 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, then xử lý là luôn luôn được thực hiện không đồng bộ - nghĩa là, sau mã bên dưới chúng không nằm trong .then xử lý.

Điều này có nghĩa là khi bạn quay trở lại data các then trình xử lý bạn đã xác định chưa thực thi. Điều này có nghĩa là giá trị bạn đang trả về chưa được đặt thành giá trị đúng theo thời gian.

Đây là một sự 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 data Là undefined kể từ khi data = 5 một phần vẫn chưa được thực hiện. Nó sẽ có khả năng thực hiện trong một giây nhưng do thời gian đó nó không liên quan đến giá trị trả lại.

Kể từ khi các hoạt động đã không xảy ra được nêu ra (AJAX, máy chủ cuộc gọi, IO, hẹn giờ) bạn đang trở về giá trị trước khi yêu cầu có cơ hội để nói với mã của bạn những gì giá trị đó là.

Một giải pháp có thể cho vấn đề này là viết mã tá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 thành. Lời hứa tích cực cho phép điều này bằng tính thời gian (nhạy cảm về thời gian) trong tự nhiên.

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

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

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

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

Khi chúng ta gọi .then trên một lời hứa và trở về một cái gì đó từ nó - chúng tôi nhận được một lời hứa cho giá trị đã xử lý. Nếu chúng ta trả lời một lời hứa khác, chúng ta sẽ 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

Hãy xem cách chúng ta có thể giải quyết vấn đề trên bằng lời hứa. Đầu tiên, hãy chứng minh sự hiểu biết của chúng ta về các trạng thái hứa hẹn từ trên cao bằng cách sử dụng Nhà xây dựng lời hứa để tạo chức năng 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ó đếm:

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 giá trị mà chúng tôi không thể thực hiện do mô hình đồng thời - chúng tôi sẽ trả về vỏ bánh cho một giá trị mà chúng ta có thể cởi mở 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 cũng giống với cuộc 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 cũng hoạt động tốt. Chúng tôi đã học được rằng chúng tôi không thể trả lại giá trị từ các cuộc gọi không đồng bộ nhưng chúng tôi có thể sử dụng lời hứa và kết nối chúng để thực hiện quá trình xử lý. Bây giờ chúng ta biết cách trả về phản hồi từ một cuộc gọi không đồng bộ.

ES2015 (ES6)

ES6 giới thiệu máy phát điện đó là các hàm có thể trả về ở giữa và sau đó tiếp tục lại điểm mà chúng đang ở. Điều này thường hữu ích cho các trình tự, 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 người lặp qua chuỗi 1,2,3,3,3,3,.... có thể được lặp lại. Trong khi điều này là thú vị trên riêng của mình và mở ra phòng cho rất nhiều khả năng có một trường hợp thú vị đặc biệt.

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

Thủ thuật hơi phức tạp nhưng rất mạnh mẽ này cho phép chúng tôi viết mã không đồng bộ theo cách đồng bộ. Có một số "người chạy" 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's Promise.coroutine ở đây, nhưng có các trình bao bọc khác như co hoặc là 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 coroutine 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, đây là tiêu 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ể await lời hứa. Đây chỉ là "đường" (cú pháp đẹp hơn) cho đề xuất ES6 ở trên bằng cách thêm async và await từ 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ả về một lời hứa giống hệt :)


245
2018-05-12 02:22



ES 2016 trông giống như giải pháp cuối cùng cho vấn đề này một lần và cho tất cả. - ShrekOverflow
Các giải pháp hoàn toàn cuối cùng, cho một lần và cho tất cả? Điều gì về giải pháp hoàn toàn cuối cùng, cho một lần và cho tất cả cộng một? - Shmiddty
nơi bạn nhận được "năm" địa ngục từ đâu? nó không bao giờ được tuyên bố ở bất kỳ đâu. - recurf
@recurf đầu tiên của tất cả "nơi địa ngục" là hầu như không thích hợp cho trang web này (trẻ em đang sử dụng nó quá, cho một). Thứ hai của tất cả - đó là một tham số chức năng, tôi có thể đặt tên nó ở bất cứ nơi nào khác và nó sẽ hoạt động tốt. - Benjamin Gruenbaum
Bạn đã trả lời câu hỏi chưa? Làm cách nào để trả lại phản hồi từ một cuộc gọi không đồng bộ? @BenjaminGruenbaum - OnPrinciples


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 đó hãy chuyển dữ liệu sang một thứ gọi là hàm gọi lại, nó 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ý gửi sẽ không làm bất cứ điều gì. Thay vào đó, bạn phải chuyển dữ liệu hoặc thực hiện những gì bạn muốn với nó trực tiếp bên trong hàm thành công.


193
2018-05-23 02:05



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ẽ hoạt động. - Jacques
Và nếu bạn muốn trả về "responseData" 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ẽ trả về nó thành "callback" 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à success phương pháp, đó là phạm vi xung quanh $.ajax. - travnik
@travnik Tôi không bỏ lỡ điều đó. Nếu bạn lấy nội dung của handleData và đặt nó vào phương thức thành công nó sẽ hoạt động giống hệt nhau ... - Jacques


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

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

185
2018-02-18 18:58



Tôi không biết ai đã bỏ phiếu cho nó tiêu cực. Nhưng đây là một công việc xung quanh mà đã làm việc trong thực tế, tôi sử dụng cách tiếp cận này để tạo ra một ứng dụng toàn bộ. Jquery.ajax không trả về dữ liệu vì vậy tốt hơn nên sử dụng phương pháp trên. Nếu nó sai thì hãy giải thích và đề nghị cách tốt hơn để làm điều đó. - Hemant Bavle
Xin lỗi, tôi quên để lại một bình luận (tôi thường làm!). Tôi downvoted nó. Downvotes không chỉ ra sự chính xác hoặc thiếu thực tế, chúng chỉ ra tính hữu dụng trong bối cảnh hoặc thiếu. Tôi không tìm thấy câu trả lời của bạn hữu ích cho Felix's mà đã giải thích điều này chỉ trong nhiều chi tiết hơn. Trên một mặt lưu ý, tại sao bạn sẽ xâu chuỗi trả lời nếu đó là JSON? - Benjamin Gruenbaum
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 vì đã làm rõ quan điểm của bạn. Sẽ ghi nhớ để đăng câu trả lời phức tạp hơn. - Hemant Bavle
Và nếu bạn muốn trả về "responseObj" bên ngoài "successCallback" ... :) ... bạn sẽ làm thế nào ...? ... gây ra một sự trở lại đơn giản sẽ trả về nó thành "callback" thành công của ajax ... và không nằm ngoài "successCallback" ... - pesho hristov


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 result Là undefined trong ví dụ mã của bạn.

enter image description here


155
2017-08-11 14:17



một bưc tranh đang gia ngan lơi noi, Người A - Yêu cầu người B chi tiết sửa xe của anh ta, lầ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, hàm Ajax thành công gọi hàm Person B và chuyển trả lời làm đối số cho nó, Person A nhận câu trả lời. - stom
Sẽ là tuyệt vời nếu bạn thêm dòng mã với mỗi hình ảnh để minh họa cho các khái niệm. - Hassan Baig
Lời giải thích tốt nhất mà tôi từng gặp - anonymous


Angular1

Đố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 rằng,

Lời hứa có thể được sử dụng để các chức năng không đồng bộ không nhất thiết và cho phép một chuỗi kết nối nhiều chức năng 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 thế.

Ví dụ được tìm thấy trong 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 Angular2 nhìn vào ví dụ sau, nhưng đề nghị sử dụng Observables vớ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ụ nó 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 nguyên đăng ở đây. Nhưng Typecript không hỗ trợ native es6 Promises, 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à những lời hứa spec xác định ở đây.


114
2017-08-26 08:11



Đ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 ở tất cả mặc dù. - Benjamin Gruenbaum
Nó không hoạt động. promiseB sẽ nhận được 'không xác định' - An Overflowed Stack
jQuery và tìm nạp cả hai phương thức đều trả về lời hứa. Tôi sẽ đề nghị sửa đổi câu trả lời của bạn. Mặc dù jQuery là không hoàn toàn giống nhau (sau đó là có, nhưng bắt không). - Tracker1


Hầu hết các câu trả lời ở đây đều đưa ra các gợi ý hữu ích khi bạn có một thao tác không đồng bộ, 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 nhập 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ụ:

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

Lý do không hoạt động là các cuộc gọi lại từ doSomethingAsync chưa chạy vào thời điểm bạn đang 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 nào đó) và muốn thực hiện các thao tác không đồng bộ cho mỗi mục nhập, bạn có hai tùy chọn: Thực hiện các phép toán song song (chồng chéo) hoặc theo chuỗi (theo thứ tự khác).

Song song, tương đông

Bạn có thể bắt đầu tất cả chúng và theo dõi số lượng cuộc gọi bạn đang 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ụ:

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

(Chúng tôi có thể làm với expecting và chỉ sử dụng results.length === theArray.length, nhưng điều đó khiến chúng ta cởi mở với khả năng theArray được thay đổi trong khi các cuộc gọi xuất sắc ...)

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

Nhưng nếu bạn cần trở về những kết quả từ một chức năng? Như các câu trả lời khác đã chỉ ra, bạn không thể; bạn phải có chức năng của bạn chấp nhận và gọi một cuộc gọi lại (hoặc trả lại một Hứa hẹn). Đâ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ụ:

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

Hoặc đây là phiên bản trả về Promise thay 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 doSomethingAsync đã thông báo lỗi cho chúng tôi, chúng tôi sẽ sử dụng reject để từ chối lời hứa khi chúng tôi gặp lỗi.)

Thí dụ:

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

(Hoặc luân phiên, bạn có thể làm cho một wrapper cho doSomethingAsync trả về một lời hứa, và sau đó làm như dưới đây ...)

Nếu doSomethingAsync cung cấp cho bạn một Hứa hẹn, bạn có thể dùng Promise.all:

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

Thí dụ:

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

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

Lưu ý rằng Promise.all giải quyết lời hứa của mình với một loạt các kết quả của tất cả các lời hứa bạn đưa ra khi tất cả chúng được giải quyết, hoặc từ chối lời hứa của nó khi Đầu tiên của những lời hứa bạn 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 chúng cái khác, bạn cần phải chờ cho mỗi thao tác hoàn tất trước khi bạn bắt đầu tiếp theo. Dưới đây là một ví dụ về một hàm 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) kể từ khi chúng tôi biết chúng tôi sẽ không nhận được kết quả ra khỏi trật tự. Ở trên chúng ta 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ụ:

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

(Hoặc, một lần nữa, xây dựng một wrapper cho doSomethingAsync cung cấp cho bạn một lời hứa và làm như dưới đây ...)

Nếu doSomethingAsync cung cấp cho bạn một lời hứa, nếu bạn có thể sử dụng cú pháp ES2017 + (có lẽ với một trình chuyển đổi như Babel), bạn có thể sử dụng async chức năng với for-of và 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);
});

Thí dụ:

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

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

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

... ít cồng kềnh hơn Hàm 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ụ:

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


91
2018-05-03 16:59



Bạn có thể giải thích cách if (--expecting === 0) một phần của mã hoạt động xin vui lòng? Các phiên bản gọi lại của giải pháp của bạn đang làm việc tuyệt vời cho 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 ứng hoàn thành. Đánh giá cao nó chỉ là thiếu kiến ​​thức về phía tôi. Có cách nào khác để kiểm tra có thể được viết không? - Sarah
@Sarah: expecting bắ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, if (--expecting === 0)làm điều này: 1. Decrements expecting (chúng tôi đã nhận được phản hồi, vì vậy chúng tôi đang mong đợi một phản hồi ít hơn) và nếu giá trị sau sự sụt giảm là 0 (chúng tôi không mong đợi thêm bất kỳ câu trả lời nào), chúng tôi đã hoàn tất! - T.J. Crowder


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

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

Đây là plnkr:

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


73
2018-06-02 08:31



đã cứu ngày của tôi! cảm ơn - uniXVanXcel


Một cách tiếp cận khác để trả về một giá trị từ mộ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ụ giống nhau:

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 cách tiếp cận này rất nhiều. Tôi sẽ được quan tâm để biết cách tiếp cận này hoạt động tốt như thế nào nơi kết nối dây trở lại thông qua các mô-đun liên tiếp có liên quan.


69
2017-09-02 12:54



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 chỉ định anh ta phản hồi trực tiếp result. Nó hoạt động bởi vì bạn đang đọc biến sau chức năng async hoàn tất. - Felix Kling