Bagaimana cara mengembalikan respons dari panggilan asinkron?

5512

Saya memiliki fungsi fooyang membuat permintaan Ajax. Bagaimana saya dapat mengembalikan respons darifoo ?

Saya mencoba mengembalikan nilai dari successcallback, serta menugaskan respons ke variabel lokal di dalam fungsi dan mengembalikan yang itu, tetapi tidak ada cara yang benar-benar mengembalikan respons.

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`.
Felix Kling
sumber

Jawaban:

5704

→ Untuk penjelasan yang lebih umum tentang perilaku async dengan contoh berbeda, silakan lihat Mengapa variabel saya tidak berubah setelah saya memodifikasinya di dalam suatu fungsi? - Referensi kode tidak sinkron

→ Jika Anda sudah memahami masalahnya, lewati ke solusi yang mungkin di bawah ini.

Masalah

The A di Ajax singkatan asynchronous . Itu berarti mengirim permintaan (atau lebih tepatnya menerima respons) dikeluarkan dari alur eksekusi normal. Dalam contoh Anda, $.ajaxkembali segera dan pernyataan berikutnya return result;,, dijalankan sebelum fungsi yang Anda lewati sebagaisuccess panggilan balik bahkan dipanggil.

Berikut ini analogi yang diharapkan membuat perbedaan antara aliran sinkron dan asinkron lebih jelas:

Sinkronis

Bayangkan Anda menelepon seorang teman dan memintanya mencari sesuatu untuk Anda. Meskipun mungkin perlu waktu, Anda menunggu di telepon dan menatap ke angkasa, sampai teman Anda memberi Anda jawaban yang Anda butuhkan.

Hal yang sama terjadi ketika Anda membuat panggilan fungsi yang berisi kode "normal":

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

var item = findItem();

// Do something with item
doSomethingElse();

Meskipun findItemmungkin membutuhkan waktu lama untuk dieksekusi, kode apa pun yang muncul var item = findItem();harus menunggu sampai fungsi mengembalikan hasilnya.

Tidak sinkron

Anda menelepon teman Anda lagi untuk alasan yang sama. Tetapi kali ini Anda memberi tahu dia bahwa Anda sedang terburu-buru dan dia harus menelepon Anda kembali di ponsel Anda. Anda menutup telepon, meninggalkan rumah dan melakukan apa pun yang Anda rencanakan. Setelah teman Anda menelepon Anda kembali, Anda berurusan dengan informasi yang dia berikan kepada Anda.

Itulah tepatnya yang terjadi ketika Anda melakukan permintaan Ajax.

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

Alih-alih menunggu respons, eksekusi berlanjut segera dan pernyataan setelah panggilan Ajax dieksekusi. Untuk mendapatkan respons pada akhirnya, Anda menyediakan fungsi untuk dipanggil setelah respons diterima, panggilan balik (perhatikan sesuatu? Panggilan balik ?). Pernyataan apa pun yang datang setelah panggilan itu dieksekusi sebelum panggilan balik dipanggil.


Solusi)

Merangkul sifat asinkron dari JavaScript!Sementara operasi asinkron tertentu menyediakan mitra sinkron (demikian juga "Ajax"), umumnya tidak disarankan untuk menggunakannya, terutama dalam konteks browser.

Mengapa ini buruk menurut Anda?

JavaScript berjalan di utas UI peramban dan proses yang berjalan lama akan mengunci UI, membuatnya tidak responsif. Selain itu, ada batas atas pada waktu eksekusi untuk JavaScript dan browser akan menanyakan kepada pengguna apakah akan melanjutkan eksekusi atau tidak.

Semua ini benar-benar pengalaman pengguna yang buruk. Pengguna tidak akan dapat mengetahui apakah semuanya berfungsi dengan baik atau tidak. Selanjutnya, efeknya akan lebih buruk bagi pengguna dengan koneksi yang lambat.

Berikut ini kita akan melihat tiga solusi berbeda yang semuanya membangun di atas satu sama lain:

  • Janji denganasync/await (ES2017 +, tersedia di browser lama jika Anda menggunakan transpiler atau regenerator)
  • Callback (populer di simpul)
  • Janji denganthen() (ES2015 +, tersedia di browser lama jika Anda menggunakan salah satu dari banyak perpustakaan janji)

Ketiganya tersedia di browser saat ini, dan node 7+.


ES2017 +: Janji dengan async/await

Versi ECMAScript yang dirilis pada 2017 memperkenalkan dukungan level sintaksis untuk fungsi-fungsi asinkron. Dengan bantuan asyncdan await, Anda dapat menulis asinkron dalam "gaya sinkron". Kode masih asinkron, tetapi lebih mudah dibaca / dimengerti.

async/awaitdibangun di atas janji: suatu asyncfungsi selalu mengembalikan janji. await"membuka" janji dan entah menghasilkan nilai yang dijanjikan dengan janji itu atau menimbulkan kesalahan jika janji itu ditolak.

Penting: Anda hanya dapat menggunakan awaitdi dalam suatu asyncfungsi. Saat ini, tingkat atas awaitbelum didukung, jadi Anda mungkin harus membuat IIFE async ( Ekspresi Fungsi yang Segera Diminta ) untuk memulaiasync konteks.

Anda dapat membaca lebih lanjut tentang asyncdan awaitdi MDN.

Berikut adalah contoh yang dibangun di atas penundaan di atas:

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

Saat peramban dan simpul versi mendukung async/await. Anda juga dapat mendukung lingkungan yang lebih lama dengan mengubah kode Anda ke ES5 dengan bantuan regenerator (atau alat yang menggunakan regenerator, seperti Babel ).


Biarkan fungsi menerima panggilan balik

Panggilan balik hanyalah sebuah fungsi yang diteruskan ke fungsi lain. Fungsi lain itu dapat memanggil fungsi yang dilewati setiap kali siap. Dalam konteks proses asinkron, callback akan dipanggil setiap kali proses asinkron dilakukan. Biasanya, hasilnya diteruskan ke panggilan balik.

Dalam contoh pertanyaan, Anda dapat foomenerima panggilan balik dan menggunakannya sebagai successpanggilan balik. Jadi ini

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

menjadi

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

Di sini kita mendefinisikan fungsi "inline" tetapi Anda dapat melewati semua referensi fungsi:

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

foo(myCallback);

foo sendiri didefinisikan sebagai berikut:

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

callbackakan merujuk ke fungsi yang kita lewati fooketika kita menyebutnya dan kita hanya meneruskannya ke success. Yaitu setelah permintaan Ajax berhasil, $.ajaxakan memanggil callbackdan meneruskan tanggapan ke panggilan balik (yang dapat dirujuk denganresult , karena ini adalah bagaimana kami mendefinisikan callback).

Anda juga dapat memproses respons sebelum meneruskannya ke callback:

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

Lebih mudah menulis kode menggunakan panggilan balik daripada yang terlihat. Lagipula, JavaScript di browser sangat didorong oleh peristiwa (peristiwa DOM). Menerima respons Ajax tidak lain adalah suatu peristiwa.
Kesulitan dapat muncul ketika Anda harus bekerja dengan kode pihak ketiga, tetapi sebagian besar masalah dapat diselesaikan dengan hanya memikirkan alur aplikasi.


ES2015 +: Janji dengan itu ()

The Promise API adalah fitur baru dari ECMAScript 6 (ES2015), tetapi memiliki baik dukungan browser sudah. Ada juga banyak perpustakaan yang mengimplementasikan standar API Janji dan menyediakan metode tambahan untuk memudahkan penggunaan dan komposisi fungsi asinkron (mis. Bluebird ).

Janji adalah wadah untuk nilai masa depan . Ketika janji menerima nilai ( diselesaikan ) atau dibatalkan ( ditolak ), ia memberi tahu semua "pendengar" yang ingin mengakses nilai ini.

Keuntungan daripada panggilan balik biasa adalah mereka memungkinkan Anda untuk memisahkan kode Anda dan mereka lebih mudah untuk menulis.

Berikut adalah contoh sederhana menggunakan janji:

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

Diterapkan ke panggilan Ajax kami, kami bisa menggunakan janji seperti ini:

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

Menjelaskan semua keuntungan yang dijanjikan menawarkan di luar cakupan jawaban ini, tetapi jika Anda menulis kode baru, Anda harus mempertimbangkannya dengan serius. Mereka memberikan abstraksi dan pemisahan kode Anda.

Informasi lebih lanjut tentang janji: HTML5 rocks - JavaScript Promises

Catatan: objek yang ditangguhkan jQuery

Objek yang ditangguhkan adalah implementasi kustom janji-janji jQuery (sebelum API Janji distandarisasi). Mereka berperilaku hampir seperti janji tetapi mengekspos API yang sedikit berbeda.

Setiap metode Ajax dari jQuery sudah mengembalikan "objek yang ditangguhkan" (sebenarnya janji objek yang ditangguhkan) yang bisa Anda kembalikan dari fungsi Anda:

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

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

Catatan: Janji Gotcha

Perlu diingat bahwa janji dan objek yang ditangguhkan hanyalah wadah untuk nilai masa depan, mereka bukan nilai itu sendiri. Misalnya, anggap Anda memiliki yang berikut:

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
}

Kode ini salah mengerti masalah asinkron di atas. Secara khusus, $.ajax()tidak membekukan kode saat memeriksa halaman '/ kata sandi' di server Anda - ia mengirim permintaan ke server dan saat menunggu, ia segera mengembalikan objek jQuery Ajax Deferred, bukan respons dari server. Itu berarti ifpernyataan akan selalu mendapatkan objek Ditangguhkan ini, perlakukan sebagai true, dan lanjutkan seolah-olah pengguna masuk. Tidak bagus.

Namun perbaikannya mudah:

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

Tidak disarankan: Panggilan "Ajax" yang disinkronkan

Seperti yang saya sebutkan, beberapa operasi asinkron (!) Memiliki mitra sinkron. Saya tidak menganjurkan penggunaannya, tetapi demi kelengkapan, berikut adalah cara Anda melakukan panggilan sinkron:

Tanpa jQuery

Jika Anda langsung menggunakan XMLHTTPRequestobjek, berikan falseargumen ketiga .open.

jQuery

Jika Anda menggunakan jQuery , Anda dapat mengatur asyncopsi ke false. Perhatikan bahwa opsi ini sudah tidak digunakan lagi sejak jQuery 1.8. Anda kemudian dapat masih menggunakan successpanggilan balik atau mengakses responseTextproperti objek jqXHR :

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

Jika Anda menggunakan metode jQuery Ajax lainnya, seperti $.get, $.getJSON, dll, Anda harus mengubahnya ke $.ajax(karena Anda hanya bisa memberikan parameter konfigurasi untuk $.ajax).

Kepala! Tidak mungkin untuk membuat permintaan JSONP sinkron . JSONP pada dasarnya selalu asinkron (satu alasan lagi untuk tidak mempertimbangkan opsi ini).

Felix Kling
sumber
74
@Pommy: Jika Anda ingin menggunakan jQuery, Anda harus memasukkannya. Silakan merujuk ke docs.jquery.com/Tutorials:Getting_Started_with_jQuery .
Felix Kling
11
Dalam Solusi 1, sub jQuery, saya tidak dapat memahami baris ini: If you use any other jQuery AJAX method, such as $.get, $.getJSON, etc., you have them to $.ajax.(Ya, saya menyadari nick saya agak ironis dalam kasus ini)
cssyphus
32
@gibberish: Mmmh, saya tidak tahu bagaimana ini bisa dibuat lebih jelas. Apakah Anda melihat bagaimana foodipanggil dan fungsi dilewatkan ke sana ( foo(function(result) {....});)? resultdigunakan di dalam fungsi ini dan merupakan respons dari permintaan Ajax. Untuk merujuk ke fungsi ini, parameter pertama dari foo dipanggil callbackdan ditetapkan successsebagai ganti fungsi anonim. Jadi, $.ajaxakan menelepon callbacksaat permintaan itu berhasil. Saya mencoba menjelaskannya sedikit lagi.
Felix Kling
43
Obrolan untuk pertanyaan ini sudah mati, jadi saya tidak yakin di mana harus mengusulkan perubahan yang diuraikan, tapi saya mengusulkan: 1) Ubah bagian sinkron ke diskusi sederhana tentang mengapa itu buruk tanpa ada contoh kode tentang bagaimana melakukannya. 2) Hapus / gabungkan contoh panggilan balik untuk hanya menunjukkan pendekatan tangguhan yang lebih fleksibel, yang saya pikir mungkin juga sedikit lebih mudah untuk diikuti bagi mereka yang belajar Javascript.
Chris Moschini
14
@ Jessi: Saya pikir Anda salah mengerti bagian dari jawaban itu. Anda tidak dapat menggunakan $.getJSONjika Anda ingin agar permintaan Ajax sinkron. Namun, Anda seharusnya tidak ingin acara disinkronkan, sehingga tidak berlaku. Anda harus menggunakan panggilan balik atau janji untuk menangani respons, seperti yang dijelaskan sebelumnya dalam jawaban.
Felix Kling
1071

Jika Anda tidak menggunakan jQuery dalam kode Anda, jawaban ini untuk Anda

Kode Anda harus berupa sesuatu seperti ini:

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 melakukan pekerjaan dengan baik menulis jawaban untuk orang yang menggunakan jQuery untuk AJAX, saya telah memutuskan untuk memberikan alternatif bagi orang yang tidak.

( Catatan, bagi mereka yang menggunakan fetchAPI baru , Sudut atau janji saya telah menambahkan jawaban lain di bawah )


Apa yang Anda hadapi

Ini adalah ringkasan singkat "Penjelasan masalah" dari jawaban yang lain, jika Anda tidak yakin setelah membaca ini, baca itu.

The A di AJAX singkatan asynchronous . Itu berarti mengirim permintaan (atau lebih tepatnya menerima respons) dikeluarkan dari alur eksekusi normal. Dalam contoh Anda, .sendkembali segera dan pernyataan berikutnya return result;,, dijalankan sebelum fungsi yang Anda berikan sebagai successpanggilan balik bahkan dipanggil.

Ini berarti ketika Anda kembali, pendengar yang Anda tetapkan belum menjalankan, yang berarti nilai yang Anda kembalikan belum ditentukan.

Berikut ini analogi sederhana

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

(Biola)

Nilai yang adikembalikan adalah undefinedkarena a=5bagian belum dieksekusi. AJAX bertindak seperti ini, Anda mengembalikan nilainya sebelum server mendapat kesempatan untuk memberi tahu browser Anda apa nilainya.

Salah satu solusi yang mungkin untuk masalah ini adalah dengan mengkode ulang secara aktif , memberi tahu program Anda apa yang harus dilakukan ketika perhitungan selesai.

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

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

Ini disebut CPS . Pada dasarnya, kami melewati getFivetindakan untuk dilakukan ketika selesai, kami memberi tahu kode kami bagaimana bereaksi ketika suatu acara selesai (seperti panggilan AJAX kami, atau dalam hal ini timeout).

Penggunaan akan:

getFive(onComplete);

Yang seharusnya mengingatkan "5" ke layar. (Biola) .

Solusi yang memungkinkan

Pada dasarnya ada dua cara bagaimana mengatasi ini:

  1. Buat panggilan AJAX sinkron (sebut saja SJAX).
  2. Merestrukturisasi kode Anda agar berfungsi dengan baik dengan callback.

1. Sinkron AJAX - Jangan lakukan itu !!

Adapun AJAX sinkron, jangan lakukan itu! Jawaban Felix menimbulkan beberapa argumen kuat tentang mengapa itu ide yang buruk. Singkatnya, ini akan membekukan browser pengguna sampai server mengembalikan respons dan menciptakan pengalaman pengguna yang sangat buruk. Berikut ini ringkasan singkat yang diambil dari MDN tentang alasan:

XMLHttpRequest mendukung komunikasi sinkron dan asinkron. Namun, secara umum, permintaan asinkron lebih disukai daripada permintaan sinkron karena alasan kinerja.

Singkatnya, permintaan sinkron memblokir eksekusi kode ... ... ini dapat menyebabkan masalah serius ...

Jika Anda harus melakukannya, Anda dapat mengibarkan bendera: Ini caranya:

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. Merestrukturisasi kode

Biarkan fungsi Anda menerima panggilan balik. Dalam contoh kode foodapat dibuat untuk menerima panggilan balik. Kami akan memberi tahu kode kami bagaimana bereaksi ketika fooselesai.

Begitu:

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

Menjadi:

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

Di sini kami melewati fungsi anonim, tetapi kami dapat dengan mudah meneruskan referensi ke fungsi yang sudah ada, membuatnya terlihat seperti:

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

Untuk detail lebih lanjut tentang bagaimana desain callback semacam ini dilakukan, periksa jawaban Felix.

Sekarang, mari kita mendefinisikan foo sendiri untuk bertindak sesuai

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

(biola)

Kami sekarang telah membuat fungsi foo kami menerima tindakan untuk dijalankan ketika AJAX selesai dengan sukses, kami dapat memperluas ini lebih jauh dengan memeriksa apakah status respons tidak 200 dan bertindak sesuai (buat penangan yang gagal dan semacamnya). Secara efektif menyelesaikan masalah kami.

Jika Anda masih kesulitan memahami ini, baca panduan memulai AJAX di MDN.

Benjamin Gruenbaum
sumber
20
"permintaan sinkron memblokir eksekusi kode dan dapat membocorkan memori dan peristiwa" bagaimana permintaan sinkron dapat membocorkan memori?
Matthew G
10
@ MatthewG Saya telah menambahkan hadiah di dalamnya dalam pertanyaan ini , saya akan melihat apa yang bisa saya memancing. Saya menghapus kutipan dari jawaban untuk sementara waktu.
Benjamin Gruenbaum
17
Hanya untuk referensi, XHR 2 memungkinkan kita untuk menggunakan onloadhandler, yang hanya menyala saat readyStateitu 4. Tentu saja, itu tidak didukung di IE8. (iirc, mungkin perlu konfirmasi.)
Florian Margaine
9
Penjelasan Anda tentang cara meneruskan fungsi anonim sebagai panggilan balik valid tetapi menyesatkan. Contoh var bar = foo (); meminta variabel untuk didefinisikan, sedangkan foo Anda yang disarankan (functim () {}); tidak mendefinisikan bilah
Robbie Averill
396

XMLHttpRequest 2 (pertama-tama baca jawaban dari Benjamin Gruenbaum & Felix Kling )

Jika Anda tidak menggunakan jQuery dan menginginkan XMLHttpRequest 2 pendek yang bagus yang berfungsi di browser modern dan juga di browser seluler, saya sarankan untuk menggunakannya dengan cara ini:

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

Seperti yang Anda lihat:

  1. Ini lebih pendek dari semua fungsi lain yang Terdaftar.
  2. Callback diatur secara langsung (jadi tidak ada penutupan tambahan yang tidak perlu).
  3. Ini menggunakan onload baru (jadi Anda tidak perlu memeriksa readystate && status)
  4. Ada beberapa situasi lain yang saya tidak ingat yang membuat XMLHttpRequest 1 mengganggu.

Ada dua cara untuk mendapatkan respons panggilan Ajax ini (tiga menggunakan nama var XMLHttpRequest):

Yang paling sederhana:

this.response

Atau jika karena alasan tertentu Anda bind()dipanggil ke kelas:

e.target.response

Contoh:

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

Atau (yang di atas adalah fungsi anonim yang lebih baik selalu menjadi masalah):

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

Tidak ada yang lebih mudah.

Sekarang beberapa orang mungkin akan mengatakan bahwa lebih baik menggunakan onreadystatechange atau bahkan nama variabel XMLHttpRequest. Itu salah.

Lihat fitur lanjutan XMLHttpRequest

Ini mendukung semua * browser modern. Dan saya dapat mengonfirmasi karena saya menggunakan pendekatan ini karena XMLHttpRequest 2 ada. Saya tidak pernah mengalami masalah pada semua browser yang saya gunakan.

onreadystatechange hanya berguna jika Anda ingin mendapatkan header pada status 2.

Menggunakan XMLHttpRequestnama variabel adalah kesalahan besar lainnya karena Anda perlu menjalankan panggilan balik di dalam penutup onload / oreadystatechange jika Anda kehilangan itu.


Sekarang jika Anda menginginkan sesuatu yang lebih kompleks menggunakan pos dan FormData Anda dapat dengan mudah memperluas fungsi ini:

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

Sekali lagi ... ini adalah fungsi yang sangat singkat, tetapi dapat & dikirim.

Contoh penggunaan:

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

Atau berikan elemen bentuk penuh ( document.getElementsByTagName('form')[0]):

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

Atau atur beberapa nilai khusus:

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

Seperti yang Anda lihat, saya tidak menerapkan sinkronisasi ... itu hal yang buruk.

Setelah mengatakan itu ... mengapa tidak melakukannya dengan cara yang mudah?


Seperti yang disebutkan dalam komentar, penggunaan kesalahan && sinkron benar-benar mematahkan inti jawabannya. Yang merupakan cara singkat yang baik untuk menggunakan Ajax dengan cara yang benar?

Penangan kesalahan

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

Dalam skrip di atas, Anda memiliki penangan kesalahan yang didefinisikan secara statis sehingga tidak mengganggu fungsi. Penangan kesalahan dapat digunakan untuk fungsi lain juga.

Tetapi untuk benar-benar keluar dari kesalahan, satu - satunya cara adalah dengan menulis URL yang salah dalam hal ini setiap browser melakukan kesalahan.

Penangan kesalahan mungkin berguna jika Anda mengatur header kustom, mengatur responseType ke buffer array gumpalan atau apa pun ...

Bahkan jika Anda melewati 'POSTAPAPAP' sebagai metode itu tidak akan menimbulkan kesalahan.

Bahkan jika Anda meneruskan 'fdggdgilfdghfldj' sebagai formdata, itu tidak akan menimbulkan kesalahan.

Dalam kasus pertama kesalahan ada di dalam di displayAjax()bawah this.statusTextsebagai Method not Allowed.

Dalam kasus kedua, itu hanya berfungsi. Anda harus memeriksa di sisi server jika Anda mengirimkan data posting yang benar.

cross-domain tidak diizinkan melempar kesalahan secara otomatis.

Dalam respons kesalahan, tidak ada kode kesalahan.

Hanya ada this.typeyang diatur ke kesalahan.

Mengapa menambahkan penangan kesalahan jika Anda benar-benar tidak memiliki kendali atas kesalahan? Sebagian besar kesalahan dikembalikan di dalamnya dalam fungsi callback displayAjax().

Jadi: Tidak perlu untuk pemeriksaan kesalahan jika Anda dapat menyalin dan menempel URL dengan benar. ;)

PS: Sebagai tes pertama saya menulis x ('x', displayAjax) ..., dan benar-benar mendapat tanggapan ... ??? Jadi saya memeriksa folder tempat HTML itu berada, dan ada file bernama 'x.xml'. Jadi, bahkan jika Anda lupa ekstensi file Anda XMLHttpRequest 2 AKAN MENCARI ITU . SAYA LOL


Baca file secara sinkron

Jangan lakukan itu.

Jika Anda ingin memblokir browser untuk sementara memuat .txtfile besar yang bagus, sinkron.

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

Sekarang kamu bisa melakukannya

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

Tidak ada cara lain untuk melakukan ini dengan cara yang tidak sinkron. (Ya, dengan loop setTimeout ... tapi serius?)

Poin lainnya adalah ... jika Anda bekerja dengan API atau hanya file daftar Anda sendiri atau apa pun yang Anda selalu gunakan fungsi berbeda untuk setiap permintaan ...

Hanya jika Anda memiliki halaman di mana Anda memuat selalu XML / JSON yang sama atau apa pun yang Anda butuhkan hanya satu fungsi. Dalam hal ini, modifikasi sedikit fungsi Ajax dan ganti b dengan fungsi khusus Anda.


Fungsi-fungsi di atas adalah untuk penggunaan dasar.

Jika Anda ingin MEMPERPANJANG fungsi ...

Ya kamu bisa.

Saya menggunakan banyak API dan salah satu fungsi pertama yang saya integrasikan ke setiap halaman HTML adalah fungsi Ajax pertama dalam jawaban ini, dengan hanya DAPATKAN ...

Tetapi Anda dapat melakukan banyak hal dengan XMLHttpRequest 2:

Saya membuat download manager (menggunakan rentang di kedua sisi dengan resume, filereader, filesystem), berbagai konverter resizers gambar menggunakan kanvas, mengisi database web SQL dengan gambar base64 dan banyak lagi ... Tetapi dalam kasus ini Anda harus membuat fungsi hanya untuk itu tujuan ... terkadang Anda membutuhkan gumpalan, buffer array, Anda dapat mengatur tajuk, mengganti mimetype dan ada banyak lagi ...

Tapi pertanyaannya di sini adalah bagaimana mengembalikan respons Ajax ... (Saya menambahkan cara mudah.)

cocco
sumber
15
Meskipun jawaban ini bagus (Dan kita semua suka XHR2 dan memposting data file dan data multi-bagian benar-benar hebat) - ini menunjukkan gula sintaksis untuk memposting XHR dengan JavaScript - Anda mungkin ingin meletakkan ini di posting blog (Saya suka) atau bahkan di perpustakaan (tidak yakin tentang namanya x, ajaxatau xhrmungkin lebih baik :)). Saya tidak melihat bagaimana alamat mengembalikan respons dari panggilan AJAX. (seseorang masih bisa melakukannya var res = x("url")dan tidak mengerti mengapa itu tidak berhasil;)) Di samping catatan - itu akan keren jika Anda kembali cdari metode sehingga pengguna dapat mengaitkan errordll.
Benjamin Gruenbaum
25
2.ajax is meant to be async.. so NO var res=x('url')..Itulah inti dari pertanyaan dan jawaban ini :)
Benjamin Gruenbaum
3
mengapa ada parameter 'c' dalam fungsi, jika pada baris pertama Anda menimpa nilai apa pun yang dimilikinya? apakah saya melewatkan sesuatu?
Brian H.
2
Anda dapat menggunakan parameter sebagai pengganti untuk menghindari menulis beberapa kali "var"
cocco
11
@cocco Jadi Anda menulis kode yang menyesatkan, tidak dapat dibaca dalam jawaban SO untuk menyimpan beberapa penekanan tombol? Tolong jangan lakukan itu.
batu
316

Jika Anda menggunakan janji, jawaban ini untuk Anda.

Ini berarti AngularJS, jQuery (dengan ditangguhkan), pengganti XHR asli (fetch), EmberJS, save BackboneJS atau pustaka node apa pun yang mengembalikan janji.

Kode Anda harus berupa sesuatu seperti ini:

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 melakukan pekerjaan dengan baik menulis jawaban untuk orang yang menggunakan jQuery dengan panggilan balik untuk AJAX. Saya punya jawaban untuk XHR asli. Jawaban ini untuk penggunaan umum janji baik di frontend atau backend.


Masalah intinya

Model konkurensi JavaScript di browser dan di server dengan NodeJS / io.js tidak sinkron dan reaktif .

Setiap kali Anda memanggil metode yang mengembalikan janji, thenpenangan selalu dieksekusi secara tidak sinkron - yaitu, setelah kode di bawahnya yang tidak ada dalam .thenpenangan.

Ini berarti ketika Anda kembali datapada thenhandler Anda menetapkan tidak mengeksekusi belum. Ini pada gilirannya berarti bahwa nilai yang Anda kembalikan belum disetel ke nilai waktu yang tepat.

Berikut ini analogi sederhana untuk masalah ini:

    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

Nilai dataini undefinedkarena data = 5bagian belum dieksekusi. Ini kemungkinan akan dieksekusi dalam sedetik tetapi pada saat itu tidak relevan dengan nilai yang dikembalikan.

Karena operasi belum terjadi (AJAX, panggilan server, IO, timer) Anda mengembalikan nilai sebelum permintaan mendapat kesempatan untuk memberi tahu kode Anda apa nilainya.

Salah satu solusi yang mungkin untuk masalah ini adalah dengan mengkode ulang secara aktif , memberi tahu program Anda apa yang harus dilakukan ketika perhitungan selesai. Janji aktif memungkinkan ini dengan menjadi temporal (sensitif waktu) di alam.

Rekap cepat janji

Janji adalah nilai dari waktu ke waktu . Janji memiliki status, mereka mulai sebagai tertunda tanpa nilai dan dapat puas dengan:

  • memenuhi artinya bahwa perhitungan selesai dengan sukses.
  • ditolak artinya penghitungan gagal.

Sebuah janji hanya dapat mengubah status satu kali setelah itu akan selalu tetap pada kondisi yang sama selamanya. Anda dapat melampirkan thenpenangan ke janji untuk mengekstraksi nilainya dan menangani kesalahan. thenpenangan memungkinkan chaining panggilan. Janji dibuat dengan menggunakan API yang mengembalikannya . Misalnya, pengganti AJAX yang lebih modern fetchatau $.getjanji pengembalian jQuery .

Ketika kita memanggil .thenjanji dan mengembalikan sesuatu darinya - kita mendapat janji untuk nilai yang diproses . Jika kita mengembalikan janji lain kita akan mendapatkan hal-hal luar biasa, tapi mari kita pegang kuda kita.

Dengan janji

Mari kita lihat bagaimana kita bisa menyelesaikan masalah di atas dengan janji-janji. Pertama, mari kita tunjukkan pemahaman kita tentang negara janji dari atas dengan menggunakan konstruktor Janji untuk membuat fungsi penundaan:

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

Sekarang, setelah kami mengonversi setTimeout untuk menggunakan janji, kita dapat menggunakannya thenuntuk membuatnya berhitung:

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

Pada dasarnya, bukannya kembali nilai yang tidak bisa kita lakukan karena model concurrency - kita mengembalikan pembungkus untuk nilai yang kita dapat membukanya dengan then. Ini seperti sebuah kotak yang bisa kamu buka then.

Menerapkan ini

Ini sama dengan panggilan API asli Anda, Anda dapat:

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

Jadi ini berfungsi juga. Kami telah mempelajari bahwa kami tidak dapat mengembalikan nilai dari panggilan yang sudah tidak sinkron tetapi kami dapat menggunakan janji dan membuat rantai untuk melakukan pemrosesan. Kami sekarang tahu cara mengembalikan respons dari panggilan asinkron.

ES2015 (ES6)

ES6 memperkenalkan generator yang merupakan fungsi yang dapat kembali di tengah dan kemudian melanjutkan titik mereka berada. Ini biasanya berguna untuk urutan, misalnya:

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

Adalah fungsi yang mengembalikan iterator di atas urutan 1,2,3,3,3,3,....yang dapat diulang. Meskipun ini menarik sendiri dan membuka ruang untuk banyak kemungkinan, ada satu kasus menarik.

Jika urutan yang kami hasilkan adalah urutan tindakan dan bukan angka - kami dapat menjeda fungsi setiap kali tindakan dihasilkan dan menunggu sebelum kami melanjutkan fungsi. Jadi, alih-alih urutan angka, kita membutuhkan urutan nilai masa depan - yaitu: janji.

Trik ini agak rumit tetapi sangat kuat memungkinkan kita menulis kode asinkron secara sinkron. Ada beberapa "pelari" yang melakukan ini untuk Anda, menulis satu adalah beberapa baris kode tetapi di luar cakupan jawaban ini. Saya akan menggunakan Bluebird di Promise.coroutinesini, tetapi ada pembungkus lain seperti coatau 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
});

Metode ini mengembalikan janji itu sendiri, yang dapat kita konsumsi dari coroutine lain. Sebagai contoh:

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)

Dalam ES7, ini lebih lanjut distandarisasi, ada beberapa proposal saat ini tetapi dalam semua itu Anda bisa awaitmenjanjikan. Ini hanya "gula" (sintaks yang lebih bagus) untuk proposal ES6 di atas dengan menambahkan asyncdanawait kata kunci. Membuat contoh di atas:

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
}

Itu masih mengembalikan janji sama saja :)

Benjamin Gruenbaum
sumber
Ini harus menjadi jawaban yang diterima. +1 untuk async / menunggu (walaupun tidak seharusnya return await data.json();?)
Lewis Donovan
247

Anda salah menggunakan Ajax. Idenya bukan untuk mengembalikan apa pun, tetapi menyerahkan data ke sesuatu yang disebut fungsi panggil balik, yang menangani data.

Itu adalah:

function handleData( responseData ) {

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

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

Mengembalikan apa pun dalam handler kirim tidak akan melakukan apa pun. Sebagai gantinya Anda harus menyerahkan data, atau melakukan apa yang Anda inginkan langsung di dalam fungsi keberhasilan.

Nic
sumber
13
Jawaban ini benar-benar semantik ... metode kesuksesan Anda hanyalah panggilan balik dalam panggilan balik. Anda bisa saja melakukannya success: handleDatadan itu akan berhasil.
Jacques ジ ャ ッ ク
5
Dan bagaimana jika Anda ingin mengembalikan "responseData" di luar "handleData" ... :) ... bagaimana Anda melakukannya ...? ... karena pengembalian sederhana akan mengembalikannya ke panggilan balik "sukses" dari ajax ... dan tidak di luar "handleData" ...
pesho hristov
@ Jacques & @pesho hristov Anda melewatkan titik ini. Kirim handler bukan successmetode, itu lingkup sekitarnya $.ajax.
travnik
@ Travnik saya tidak ketinggalan. Jika Anda mengambil konten dari handleData dan memasukkannya ke dalam metode keberhasilan itu akan bertindak persis sama ...
Jacques ジ ャ ッ ク
234

Solusi paling sederhana adalah membuat fungsi JavaScript dan memanggilnya untuk successpanggilan balik 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);    
}); 
Hemant Bavle
sumber
3
Saya tidak tahu siapa yang memberikan suara negatif. Tapi ini adalah pekerjaan yang telah bekerja sebenarnya saya menggunakan pendekatan ini untuk membuat aplikasi secara keseluruhan. Jquery.ajax tidak mengembalikan data sehingga lebih baik menggunakan pendekatan di atas. Jika itu salah maka tolong jelaskan dan sarankan cara yang lebih baik untuk melakukannya.
Hemant Bavle
11
Maaf, saya lupa meninggalkan komentar (biasanya saya lakukan!). Saya menurunkannya. Downvotes tidak menunjukkan kebenaran faktual atau kurangnya, mereka menunjukkan kegunaan dalam konteks atau kurangnya. Saya tidak menemukan jawaban Anda berguna mengingat Felix yang sudah menjelaskan ini hanya dengan lebih detail. Di samping catatan, mengapa Anda merangkai respons jika JSON?
Benjamin Gruenbaum
5
ok .. @Benjamin saya menggunakan stringify, untuk mengkonversi objek JSON menjadi string. Dan terima kasih telah menjelaskan poin Anda. Akan diingat untuk mengirim jawaban yang lebih rumit.
Hemant Bavle
Dan bagaimana jika Anda ingin mengembalikan "responseObj" di luar "successCallback" ... :) ... bagaimana Anda melakukannya ...? ... karena pengembalian sederhana akan mengembalikannya ke panggilan balik "sukses" dari ajax ... dan tidak di luar "successCallback" ...
pesho hristov
221

Saya akan menjawab dengan komik yang terlihat mengerikan dan digambar tangan. Gambar kedua adalah alasan mengapa resultada undefineddalam contoh kode Anda.

masukkan deskripsi gambar di sini

Johannes Fahrenkrug
sumber
32
Sebuah gambar bernilai seribu kata , Orang A - Tanyakan detail orang B untuk memperbaiki mobilnya, sebaliknya Orang B - Membuat Panggilan Ajax dan menunggu respons dari server untuk detail perbaikan mobil, ketika respons diterima, fungsi Sukses Ajax memanggil Orang tersebut B berfungsi dan meneruskan respons sebagai argumen padanya, Orang A menerima jawabannya.
shaijut
10
Akan lebih bagus jika Anda menambahkan baris kode dengan setiap gambar untuk menggambarkan konsep.
Hassan Baig
1
Sementara itu, pria dengan mobil terjebak di sisi jalan. Dia mengharuskan mobilnya diperbaiki sebelum melanjutkan. Dia sekarang sendirian di sisi jalan menunggu ... Dia lebih suka di telepon menunggu perubahan status tetapi mekanik tidak akan melakukannya ... Mekanik mengatakan dia harus melanjutkan pekerjaannya dan tidak bisa cukup nongkrong di telepon. Mechanic berjanji akan meneleponnya kembali secepat mungkin. Setelah sekitar 4 jam, pria itu menyerah dan memanggil Uber. - Contoh batas waktu.
barrypicker
@barrypicker :-D Brilliant!
Johannes Fahrenkrug
159

Angular1

Bagi orang yang menggunakan AngularJS , dapat menangani situasi ini menggunakan Promises.

Di sini dikatakan,

Janji dapat digunakan untuk merusak fungsi asinkron dan memungkinkan seseorang untuk menghubungkan beberapa fungsi secara bersamaan.

Anda juga dapat menemukan penjelasan yang bagus di sini .

Contoh ditemukan dalam dokumen yang disebutkan di bawah ini.

  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 dan Selanjutnya

Masuk Angular2dengan melihat contoh berikut, tetapi disarankan untuk digunakan Observablesbersama Angular2.

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

}

Anda dapat mengkonsumsinya dengan cara ini,

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

Lihat posting asli di sini. Tapi Typescript tidak mendukung Janji es6 asli , jika Anda ingin menggunakannya, Anda mungkin perlu plugin untuk itu.

Selain itu di sini adalah spesifikasi janji yang ditentukan di sini.

Maleen Abewardana
sumber
15
Ini tidak menjelaskan bagaimana janji akan menyelesaikan masalah ini sama sekali.
Benjamin Gruenbaum
4
jQuery dan metode pengambilan keduanya memberikan janji juga. Saya sarankan merevisi jawaban Anda. Meskipun jQuery tidak persis sama (lalu ada di sana, tetapi tangkapan tidak).
Pelacak1
153

Sebagian besar jawaban di sini memberikan saran yang berguna untuk ketika Anda memiliki operasi async tunggal, tetapi kadang-kadang, ini muncul ketika Anda perlu melakukan operasi asinkron untuk setiap entri dalam array atau struktur daftar-suka lainnya. Godaan adalah melakukan ini:

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

Contoh:

Alasan yang tidak berfungsi adalah bahwa callback dari doSomethingAsyncbelum berjalan pada saat Anda mencoba menggunakan hasilnya.

Jadi, jika Anda memiliki array (atau daftar sejenis) dan ingin melakukan operasi asink untuk setiap entri, Anda memiliki dua opsi: Lakukan operasi secara paralel (tumpang tindih), atau secara seri (satu demi satu secara berurutan).

Paralel

Anda dapat memulai semuanya dan melacak berapa banyak panggilan balik yang Anda harapkan, dan kemudian menggunakan hasilnya ketika Anda mendapatkan banyak panggilan balik itu:

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

Contoh:

(Kita bisa menghapus expectingdan hanya menggunakan results.length === theArray.length, tapi itu membuat kita terbuka untuk kemungkinan yang theArraydiubah saat panggilan luar biasa ...)

Perhatikan bagaimana kami menggunakan indexdari forEachuntuk menyimpan hasilnyaresults posisi yang sama dengan entri yang terkait, bahkan jika hasilnya tiba di luar urutan (karena panggilan async tidak harus lengkap dalam urutan di mana mereka mulai).

Tetapi bagaimana jika Anda perlu mengembalikan hasil dari suatu fungsi? Seperti yang ditunjukkan oleh jawaban lain, Anda tidak bisa; Anda harus memiliki fungsi Anda menerima dan memanggil panggilan balik (atau mengembalikan Janji ). Ini versi panggilan balik:

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

Contoh:

Atau ini versi mengembalikan Promisegantinya:

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

Tentu saja, jika doSomethingAsyncmelewati kami kesalahan, kami akan gunakan rejectuntuk menolak janji ketika kami mendapat kesalahan.)

Contoh:

(Atau secara bergantian, Anda bisa membuat pembungkus untuk doSomethingAsyncitu mengembalikan janji, dan kemudian melakukan yang di bawah ...)

Jika doSomethingAsyncmemberi Anda Janji , Anda dapat menggunakan Promise.all:

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

Jika Anda tahu itu doSomethingAsyncakan mengabaikan argumen kedua dan ketiga, Anda bisa langsung menyampaikannya map( mappanggilan balik dengan tiga argumen, tetapi kebanyakan orang hanya menggunakan sebagian besar pertama kali):

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

Contoh:

Perhatikan bahwa Promise.allselesaikan janjinya dengan serangkaian hasil dari semua janji yang Anda berikan saat semuanya dipenuhi, atau tolak janjinya ketika yang pertama dari janji yang Anda berikan itu tolak.

Seri

Misalkan Anda tidak ingin operasi dilakukan secara paralel? Jika Anda ingin menjalankannya satu per satu, Anda harus menunggu setiap operasi selesai sebelum Anda memulai yang berikutnya. Berikut adalah contoh fungsi yang melakukan itu dan memanggil panggilan balik dengan hasilnya:

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

(Karena kami melakukan pekerjaan secara seri, kami hanya dapat menggunakan results.push(result)karena kami tahu kami tidak akan mendapatkan hasil yang salah. Di atas kita bisa menggunakan results[index] = result;, tetapi dalam beberapa contoh berikut kita tidak memiliki indeks menggunakan.)

Contoh:

(Atau, sekali lagi, buat pembungkus untuk doSomethingAsyncitu memberi Anda janji dan lakukan yang di bawah ini ...)

Jika doSomethingAsyncmemberi Anda Janji, jika Anda dapat menggunakan sintaks ES2017 + (mungkin dengan transpiler seperti Babel ), Anda dapat menggunakanasync fungsi dengan for-ofdan 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);
});

Contoh:

Jika Anda tidak dapat menggunakan sintaks ES2017 + (belum), Anda dapat menggunakan variasi pada "Janji pengurangan" (ini lebih kompleks daripada pengurangan Janji yang biasa karena kami tidak meneruskan hasil dari satu ke yang berikutnya, tetapi sebagai gantinya mengumpulkan hasil mereka dalam array):

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

Contoh:

... yang kurang rumit dengan fungsi panah 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);
});

Contoh:

TJ Crowder
sumber
1
Bisakah Anda menjelaskan bagaimana if (--expecting === 0)bagian dari kode itu bekerja? Versi callback dari solusi Anda berfungsi baik untuk saya, saya hanya tidak mengerti bagaimana, dengan pernyataan itu, Anda memeriksa jumlah respons yang diselesaikan. Menghargai itu hanya karena kurangnya pengetahuan di pihak saya. Apakah ada cara alternatif agar cek dapat ditulis?
Sarah
@ Sarah: expectingdimulai dengan nilai array.length, yaitu berapa banyak permintaan yang akan kami buat. Kami tahu bahwa panggilan balik itu tidak akan dipanggil sampai semua permintaan itu dimulai. Dalam callback, if (--expecting === 0)lakukan ini: 1. Pengurangan expecting(kami telah menerima respons, jadi kami mengharapkan satu tanggapan lebih sedikit) dan jika nilai setelah penurunan adalah 0 (kami tidak mengharapkan tanggapan lagi), kami selesai!
TJ Crowder
1
@ Patrickrickoberts - Terima kasih !! Ya, kesalahan salin dan tempel, argumen kedua itu benar-benar diabaikan dalam contoh itu (yang merupakan satu-satunya alasan ia tidak gagal, karena seperti yang Anda tunjukkan, resultstidak ada). :-) Memperbaikinya.
TJ Crowder
111

Lihat contoh ini:

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

Seperti yang Anda lihat getJokeadalah mengembalikan janji yang telah diselesaikan (itu diselesaikan saat kembali res.data.value). Jadi Anda menunggu sampai permintaan $ http.get selesai dan kemudian console.log (res.joke) dieksekusi (sebagai aliran asinkron normal).

Ini adalah plnkr:

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

Cara ES6 (async - tunggu)

(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);
  });
})();
Francisco Carmona
sumber
107

Ini adalah salah satu tempat di mana dua cara pengikatan data atau konsep penyimpanan yang digunakan dalam banyak kerangka kerja JavaScript baru akan bekerja sangat baik untuk Anda ...

Jadi jika Anda menggunakan Angular, React atau kerangka kerja lain yang melakukan dua cara pengikatan data atau konsep menyimpan masalah ini hanya diperbaiki untuk Anda, jadi dengan kata lain, hasilnya adalah undefinedpada tahap pertama, jadi Anda sudah mendapatkannya result = undefinedsebelum Anda menerima data, maka segera setelah Anda mendapatkan hasilnya, itu akan diperbarui dan ditugaskan ke nilai baru yang respons panggilan Ajax Anda ...

Tetapi bagaimana Anda bisa melakukannya dalam javascript atau jQuery murni misalnya ketika Anda bertanya dalam pertanyaan ini?

Anda dapat menggunakan panggilan balik , janji dan baru-baru ini diamati untuk menanganinya untuk Anda, misalnya dalam janji-janji kami memiliki beberapa fungsi seperti success()atau then()yang akan dieksekusi ketika data Anda siap untuk Anda, sama dengan fungsi panggil balik atau berlangganan pada diamati .

Misalnya dalam kasus Anda yang Anda gunakan jQuery , Anda dapat melakukan sesuatu seperti ini:

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

Untuk informasi lebih lanjut, pelajari tentang janji dan yang dapat diamati yang merupakan cara baru untuk melakukan hal ini.

Alireza
sumber
Ini bagus di lingkup global, tetapi dalam beberapa konteks modul Anda mungkin ingin memastikan konteks yang tepat untuk callback misalnya$.ajax({url: "api/data", success: fooDone.bind(this)});
steve.sims
8
Ini sebenarnya salah karena Bereaksi mengikat data satu arah
Matthew Brent
@ MatthewBrent Anda tidak salah, tetapi tidak benar juga, Bereaksi props adalah objek dan jika diubah, mereka berubah di seluruh aplikasi, tetapi itu bukan cara yang direkomendasikan pengembang Bereaksi untuk menggunakannya ...
Alireza
98

Ini adalah masalah yang sangat umum yang kita hadapi saat berjuang dengan 'misteri' JavaScript. Biarkan saya mencoba mengungkap misteri ini hari ini.

Mari kita mulai dengan fungsi JavaScript sederhana:

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

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

Itu adalah panggilan fungsi sinkron sederhana (di mana setiap baris kode 'selesai dengan tugasnya' sebelum yang berikutnya secara berurutan), dan hasilnya sama seperti yang diharapkan.

Sekarang mari kita tambahkan sedikit twist, dengan memperkenalkan sedikit delay pada fungsi kita, sehingga semua baris kode tidak 'selesai' secara berurutan. Dengan demikian, ini akan meniru perilaku asinkron fungsi:

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

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

Jadi begitulah, penundaan itu baru saja merusak fungsionalitas yang kami harapkan! Tetapi apa yang sebenarnya terjadi? Sebenarnya cukup logis jika Anda melihat kode. fungsi foo(), setelah eksekusi, tidak mengembalikan apa-apa (dengan demikian nilai yang dikembalikan undefined), tetapi ia memulai timer, yang mengeksekusi fungsi setelah 1s untuk mengembalikan 'wohoo'. Tetapi seperti yang Anda lihat, nilai yang ditugaskan ke bar adalah barang yang langsung dikembalikan dari foo (), yang artinya tidak adil undefined.

Jadi, bagaimana kita mengatasi masalah ini?

Mari kita tanyakan fungsi kita untuk PROMISE . Janji benar-benar tentang apa artinya: itu berarti bahwa fungsi menjamin Anda untuk memberikan dengan output apa pun di masa depan. jadi mari kita lihat dalam aksi untuk masalah kecil kita di atas:

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

Dengan demikian, ringkasannya adalah - untuk menangani fungsi asinkron seperti panggilan berbasis ajax dll., Anda dapat menggunakan janji dengan resolvenilai (yang ingin Anda kembalikan). Jadi, singkatnya Anda menyelesaikan nilai alih-alih mengembalikan , dalam fungsi asinkron.

UPDATE (Janji dengan async / menunggu)

Selain menggunakan then/catchuntuk bekerja dengan janji-janji, ada satu pendekatan lagi. Idenya adalah untuk mengenali fungsi asinkron dan kemudian menunggu janji untuk diselesaikan, sebelum pindah ke baris kode berikutnya. Itu masih sajapromises bawah kap, tetapi dengan pendekatan sintaksis yang berbeda. Agar lebih jelas, Anda dapat menemukan perbandingan di bawah ini:

lalu / tangkap versi:

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

versi async / menunggu:

  async function saveUsers(){
     try{
        let users = await getUsers()
        saveSomewhere(users);
     }
     catch(err){
        console.error(err);
     }
  }
Anish K.
sumber
apakah ini masih dianggap cara terbaik untuk mengembalikan nilai dari janji atau async / menunggu?
edwardsmarkf
3
@edwardsmarkf Secara pribadi saya tidak berpikir ada cara terbaik seperti itu. Saya menggunakan janji dengan / catch, async / menunggu serta generator untuk bagian async dari kode saya. Ini sangat tergantung pada konteks penggunaan.
Anish K.
96

Pendekatan lain untuk mengembalikan nilai dari fungsi asinkron, adalah untuk meneruskan objek yang akan menyimpan hasil dari fungsi asinkron.

Berikut adalah contoh yang sama:

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

Saya menggunakan result objek untuk menyimpan nilai selama operasi asinkron. Ini memungkinkan hasilnya tersedia bahkan setelah pekerjaan asinkron.

Saya sering menggunakan pendekatan ini. Saya akan tertarik untuk mengetahui seberapa baik pendekatan ini bekerja di mana kabel hasil kembali melalui modul yang terlibat.

jsbisht
sumber
9
Tidak ada yang istimewa tentang menggunakan objek di sini. Ini akan bekerja juga jika Anda menugaskannya untuk merespons langsung result. Ini berfungsi karena Anda membaca variabel setelah fungsi async selesai.
Felix Kling
85

Sementara janji dan panggilan balik berfungsi dengan baik dalam banyak situasi, itu adalah rasa sakit di belakang untuk mengekspresikan sesuatu seperti:

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

Anda akhirnya akan melalui async1; periksa apakah nametidak terdefinisi atau tidak dan panggil panggilan balik yang sesuai.

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

async1(name, async2)

Meskipun tidak apa - apa dalam contoh-contoh kecil, itu akan mengganggu ketika Anda memiliki banyak kasus serupa dan penanganan kesalahan yang terlibat.

Fibers membantu dalam memecahkan masalah.

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
}

Anda dapat checkout proyek di sini .

rohithpr
sumber
1
@recurf - Ini bukan proyek saya. Anda dapat mencoba menggunakan pelacak masalah mereka.
rohithpr
1
apakah ini mirip dengan fungsi generator? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… *
Emanegux
1
Apakah ini masih relevan?
Aluan Haddad
Anda dapat memanfaatkan async-awaitjika Anda menggunakan beberapa versi terbaru dari node. Jika seseorang terjebak dengan versi yang lebih lama, mereka dapat menggunakan metode ini.
rohithpr
83

Contoh berikut yang saya tulis menunjukkan caranya

  • Menangani panggilan HTTP asinkron;
  • Tunggu respons dari setiap panggilan API;
  • Gunakan pola Janji ;
  • Gunakan pola Promise.all untuk bergabung dengan beberapa panggilan HTTP;

Contoh kerja ini mandiri. Ini akan menentukan objek permintaan sederhana yang menggunakan XMLHttpRequestobjek jendela untuk melakukan panggilan. Ini akan menentukan fungsi sederhana untuk menunggu banyak janji untuk diselesaikan.

Konteks. Contohnya adalah menanyakan titik akhir Spotify Web API untuk mencari playlistobjek untuk serangkaian string kueri yang diberikan:

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

Untuk setiap item, Janji baru akan mem-blok - ExecutionBlock, parsing hasilnya, jadwalkan satu set janji baru berdasarkan pada array hasil, yaitu daftar userobjek Spotify dan jalankan panggilan HTTP baru dalam ExecutionProfileBlockasinkron.

Anda kemudian dapat melihat struktur Janji bersarang, yang memungkinkan Anda menelurkan beberapa panggilan HTTP bersarang tidak bersinkronisasi, dan bergabung dengan hasil dari setiap subset panggilan Promise.all.

CATATANsearch API Spotify Terbaru akan membutuhkan token akses untuk ditentukan dalam header permintaan:

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

Jadi, Anda menjalankan contoh berikut ini Anda harus memasukkan token akses Anda di header permintaan:

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

Saya telah banyak membahas solusi ini di sini .

loretoparisi
sumber
80

Jawaban singkatnya adalah, Anda harus menerapkan panggilan balik seperti ini:

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

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

2017 jawaban: sekarang Anda dapat melakukan apa yang Anda inginkan di setiap browser dan node saat ini

Ini cukup sederhana:

  • Kembalikan Janji
  • Gunakan 'menunggu' , yang akan memberi tahu JavaScript untuk menunggu janji untuk diselesaikan menjadi suatu nilai (seperti respons HTTP)
  • Tambahkan kata kunci 'async' ke fungsi induk

Ini versi kode Anda yang berfungsi:

(async function(){

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

})()

await didukung di semua browser dan node 8 saat ini

mikemaccana
sumber
7
Sayangnya, ini hanya berfungsi dengan fungsi yang mengembalikan janji - misalnya itu tidak berfungsi dengan Node.js API, yang menggunakan panggilan balik. Dan saya tidak akan merekomendasikan menggunakannya tanpa Babel, karena tidak semua orang menggunakan "browser saat ini".
Michał Perłakowski
2
@ MichałPerłakowski node 8 termasuk nodejs.org/api/util.html#util_util_promisify_original yang dapat digunakan untuk membuat API node.js mengembalikan janji. Apakah Anda punya waktu dan uang untuk mendukung browser yang tidak lancar jelas tergantung pada situasi Anda.
mikemaccana
IE 11 masih merupakan browser saat ini pada tahun 2018, sayangnya dan tidak mendukungawait/async
Juan Mendes
IE11 bukan browser saat ini. Ini dirilis 5 tahun yang lalu, memiliki pangsa pasar di seluruh dunia 2,5% menurut caniuse, dan kecuali seseorang menggandakan anggaran Anda untuk mengabaikan semua teknologi saat ini maka itu tidak sepadan dengan waktu kebanyakan orang.
mikemaccana
76

Js adalah utas tunggal.

Browser dapat dibagi menjadi tiga bagian:

1) Kejadian

2) API Web

3) Antrian Acara

Event Loop berjalan selamanya yaitu jenis infinite loop. Event Queue adalah tempat semua fungsi Anda didorong pada beberapa acara (contoh: klik) ini adalah satu per satu dilakukan antrian dan dimasukkan ke dalam loop acara yang menjalankan fungsi ini dan menyiapkannya sendiri untuk yang berikutnya setelah yang pertama dieksekusi. Ini berarti Eksekusi satu fungsi tidak dimulai sampai fungsi sebelum dalam antrian dieksekusi dalam loop peristiwa.

Sekarang mari kita berpikir kita mendorong dua fungsi dalam antrian satu untuk mendapatkan data dari server dan yang lain memanfaatkan data tersebut. Kami mendorong fungsi serverRequest () dalam antrian terlebih dahulu kemudian menggunakan fungsiiseiseData (). Fungsi serverRequest berjalan dalam event loop dan membuat panggilan ke server karena kita tidak pernah tahu berapa banyak waktu yang diperlukan untuk mendapatkan data dari server sehingga proses ini diharapkan memakan waktu dan jadi kita sibuk acara loop kita sehingga menggantung halaman kita, di situlah Web API berperan mengambil fungsi ini dari loop acara dan berurusan dengan server membuat loop acara bebas sehingga kita dapat menjalankan fungsi berikutnya dari antrian. Fungsi berikutnya dalam antrian adalah utiliseData () yang berjalan dalam loop tetapi karena tidak ada data yang tersedia ia pergi pemborosan dan eksekusi fungsi selanjutnya berlanjut sampai akhir antrian. (Ini disebut panggilan Async yaitu kita dapat melakukan sesuatu yang lain sampai kita mendapatkan data)

Misalkan saja fungsi serverRequest () kami memiliki pernyataan pengembalian dalam kode, ketika kami mendapatkan kembali data dari server Web API akan mendorongnya dalam antrian di akhir antrian. Karena terdesak di ujung antrian, kami tidak dapat memanfaatkan datanya karena tidak ada fungsi yang tersisa di antrian kami untuk memanfaatkan data ini. Dengan demikian, tidak mungkin mengembalikan sesuatu dari Panggilan Async.

Jadi Solusi untuk ini adalah panggilan balik atau janji .

Gambar dari salah satu jawaban di sini, Menjelaskan dengan benar penggunaan panggilan balik ... Kami memberikan fungsi kami (fungsi menggunakan data yang dikembalikan dari server) ke fungsi memanggil server.

CallBack

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

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


  }
  xmlHttpReq.send(null);

}

Dalam Kode saya disebut sebagai

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

Callback Javscript.info

Aniket Jha
sumber
68

Anda dapat menggunakan perpustakaan khusus ini (ditulis menggunakan Janji) untuk membuat panggilan jarak jauh.

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

Contoh penggunaan sederhana:

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

Solusi lain adalah dengan mengeksekusi kode melalui nsynjs eksekusi berurutan .

Jika fungsi yang mendasarinya dijanjikan

nsynjs akan mengevaluasi semua janji secara berurutan, dan memasukkan hasil janji ke dalam dataproperti:

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>

Jika fungsi yang mendasarinya tidak dijanjikan

Langkah 1. Bungkus fungsi dengan callback ke nsynjs-aware wrapper (jika memiliki versi yang dijanjikan, Anda dapat melewati langkah ini):

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;

Langkah 2. Masukkan logika sinkron ke dalam fungsi:

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

Langkah 3. Jalankan fungsi secara sinkron melalui nsynjs:

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

Nsynjs akan mengevaluasi semua operator dan ekspresi langkah demi langkah, menghentikan sementara eksekusi jika hasil dari beberapa fungsi yang lambat tidak siap.

Contoh lebih lanjut di sini: https://github.com/amaksr/nsynjs/tree/master/examples

amaksr
sumber
2
Ini menarik. Saya suka bagaimana ini memungkinkan untuk memanggil kode async seperti yang Anda lakukan dalam bahasa lain. Tapi secara teknis itu bukan JavaScript asli?
J Morris
41

ECMAScript 6 memiliki 'generator' yang memungkinkan Anda untuk dengan mudah memprogram dalam gaya asinkron.

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

Untuk menjalankan kode di atas Anda melakukan ini:

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

Jika Anda perlu menargetkan browser yang tidak mendukung ES6 Anda dapat menjalankan kode melalui Babel atau closure-compiler untuk menghasilkan ECMAScript 5.

Callback ...argsdibungkus dalam array dan dihancurkan ketika Anda membacanya sehingga polanya dapat mengatasi panggilan balik yang memiliki banyak argumen. Misalnya dengan simpul fs :

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

Berikut adalah beberapa pendekatan untuk bekerja dengan permintaan asinkron:

  1. Objek Browser Janji
  2. Q - Perpustakaan janji untuk JavaScript
  3. A + Promises.js
  4. jQuery ditangguhkan
  5. API XMLHttpRequest
  6. Menggunakan konsep panggilan balik - Sebagai implementasi dalam jawaban pertama

Contoh: jQuery implementasi yang ditangguhkan untuk bekerja dengan beberapa permintaan

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

Mohan Dere
sumber
38

Kita menemukan diri kita di alam semesta yang tampak maju sepanjang dimensi yang kita sebut "waktu". Kami tidak benar-benar mengerti apa waktu itu, tetapi kami telah mengembangkan abstraksi dan kosa kata yang membuat kami beralasan dan membicarakannya: "masa lalu", "sekarang", "masa depan", "sebelum", "setelah".

Sistem komputer yang kami bangun - semakin banyak - memiliki waktu sebagai dimensi penting. Hal-hal tertentu diatur untuk terjadi di masa depan. Maka hal-hal lain perlu terjadi setelah hal-hal pertama itu akhirnya terjadi. Ini adalah gagasan dasar yang disebut "asinkronisitas". Dalam dunia kita yang semakin berjejaring, kasus asynchronicity yang paling umum adalah menunggu sistem jarak jauh untuk menanggapi beberapa permintaan.

Pertimbangkan sebuah contoh. Anda memanggil pengantar susu dan memesan susu. Ketika datang, Anda ingin memasukkannya ke dalam kopi Anda. Anda tidak dapat memasukkan susu ke dalam kopi Anda sekarang, karena belum ada di sini. Anda harus menunggu sampai datang sebelum memasukkannya ke dalam kopi Anda. Dengan kata lain, berikut ini tidak akan berfungsi:

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

Karena JS tidak memiliki cara untuk mengetahui bahwa perlu menunggu untuk order_milksampai akhir sebelum dijalankan put_in_coffee. Dengan kata lain, itu tidak tahu bahwa itu order_milktidak sinkron - adalah sesuatu yang tidak akan menghasilkan susu sampai beberapa waktu ke depan. JS, dan bahasa deklaratif lainnya menjalankan satu pernyataan demi satu tanpa menunggu.

Pendekatan JS klasik untuk masalah ini, mengambil keuntungan dari fakta bahwa JS mendukung fungsi sebagai objek kelas satu yang dapat diteruskan, adalah meneruskan fungsi sebagai parameter ke permintaan asinkron, yang kemudian akan dipanggil ketika telah selesai tugasnya di masa depan. Itu adalah pendekatan "panggilan balik". Ini terlihat seperti ini:

order_milk(put_in_coffee);

order_milkdimulai, memesan susu, kemudian, kapan dan hanya ketika itu tiba, itu memanggil put_in_coffee.

Masalah dengan pendekatan callback ini adalah bahwa ia mencemari semantik normal dari suatu fungsi yang melaporkan hasilnya return; sebagai gantinya, fungsi tidak boleh melaporkan hasilnya dengan memanggil panggilan balik yang diberikan sebagai parameter. Juga, pendekatan ini dapat dengan cepat menjadi sulit ketika berhadapan dengan urutan kejadian yang lebih panjang. Sebagai contoh, katakanlah saya ingin menunggu susu dimasukkan ke dalam kopi, dan baru kemudian melakukan langkah ketiga, yaitu minum kopi. Saya akhirnya perlu menulis sesuatu seperti ini:

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

di mana saya melewati put_in_coffeekedua susu untuk dimasukkan ke dalamnya, dan juga tindakan ( drink_coffee) untuk mengeksekusi setelah susu dimasukkan. Kode tersebut menjadi sulit untuk ditulis, dibaca, dan didebug.

Dalam hal ini, kita dapat menulis ulang kode dalam pertanyaan sebagai:

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

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

Masukkan janji

Ini adalah motivasi untuk gagasan "janji", yang merupakan jenis nilai tertentu yang mewakili semacam hasil di masa depan atau tidak sinkron . Itu bisa mewakili sesuatu yang sudah terjadi, atau yang akan terjadi di masa depan, atau mungkin tidak pernah terjadi sama sekali. Janji-janji memiliki satu metode, yang dinamai then, tempat Anda meloloskan suatu tindakan untuk dieksekusi ketika hasil yang dijanjikan janji telah terwujud.

Dalam hal susu dan kopi kami, kami merancang order_milkuntuk mengembalikan janji atas kedatangan susu, kemudian menetapkan put_in_coffeesebagai thentindakan, sebagai berikut:

order_milk() . then(put_in_coffee)

Satu keuntungan dari ini adalah kita dapat merangkai ini bersama-sama untuk menciptakan urutan kejadian di masa depan ("rantai"):

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

Mari kita terapkan janji untuk masalah khusus Anda. Kami akan membungkus logika permintaan kami di dalam suatu fungsi, yang mengembalikan janji:

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

Sebenarnya, semua yang kami lakukan ditambahkan returnke panggilan $.ajax. Ini berfungsi karena jQuery $.ajaxsudah mengembalikan semacam janji. (Dalam praktiknya, tanpa masuk ke perincian, kami lebih suka untuk membungkus panggilan ini sehingga untuk mengembalikan janji nyata, atau menggunakan beberapa alternatif untuk $.ajaxmelakukannya.) Sekarang, jika kita ingin memuat file dan menunggu sampai selesai dan lalu lakukan sesuatu, kita bisa langsung mengatakannya

get_data() . then(do_something)

contohnya,

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

Saat menggunakan janji, kami akhirnya memberikan banyak fungsi then, jadi sering kali membantu menggunakan fungsi panah gaya ES6 yang lebih ringkas:

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

Kata asynckunci

Tetapi masih ada sesuatu yang agak tidak memuaskan karena harus menulis kode satu arah jika sinkron dan cara yang sangat berbeda jika tidak sinkron. Untuk sinkron, kami menulis

a();
b();

tetapi jika atidak sinkron, dengan janji kita harus menulis

a() . then(b);

Di atas, kami berkata, "JS tidak memiliki cara untuk mengetahui bahwa ia perlu menunggu panggilan pertama selesai sebelum mengeksekusi yang kedua". Bukankah lebih baik jika ada adalah beberapa cara untuk memberitahu JS itu? Ternyata ada - awaitkata kunci, yang digunakan di dalam jenis fungsi khusus yang disebut fungsi "async". Fitur ini adalah bagian dari versi ES yang akan datang, tetapi sudah tersedia dalam transpiler seperti Babel yang diberikan preset yang tepat. Ini memungkinkan kita untuk sekadar menulis

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

Dalam kasus Anda, Anda dapat menulis sesuatu seperti

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

Jawaban singkat : foo()Metode Anda segera kembali, sementara $ajax()panggilan dijalankan secara serempak setelah fungsi kembali . Masalahnya kemudian bagaimana atau di mana menyimpan hasil yang diambil oleh panggilan async setelah itu kembali.

Beberapa solusi telah diberikan di utas ini. Mungkin cara termudah adalah dengan mengirimkan objek ke foo()metode, dan menyimpan hasilnya di anggota objek itu setelah panggilan async selesai.

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

Perhatikan bahwa panggilan ke foo()masih tidak menghasilkan apa-apa yang berguna. Namun, hasil dari panggilan async sekarang akan disimpan di result.response.

David R Tribble
sumber
14
Meskipun ini berfungsi, itu tidak benar-benar lebih baik daripada menugaskan ke variabel global.
Felix Kling
36

Gunakan callback()fungsi di dalam foo()kesuksesan. Coba dengan cara ini. Sederhana dan mudah dimengerti.  

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();
Mahfuzur Rahman
sumber
29

Pertanyaannya adalah:

Bagaimana cara mengembalikan respons dari panggilan asinkron?

yang BISA diartikan sebagai:

Bagaimana cara membuat kode asinkron terlihat sinkron ?

Solusinya adalah menghindari callback, dan menggunakan kombinasi dari Janji dan async / menunggu .

Saya ingin memberi contoh untuk permintaan Ajax.

(Meskipun dapat ditulis dalam Javascript, saya lebih suka menulisnya dengan Python, dan mengompilasinya ke Javascript menggunakan Transcrypt . Akan cukup jelas.)

Pertama mari kita aktifkan penggunaan JQuery, agar $tersedia sebagai S:

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

Tetapkan fungsi yang mengembalikan Janji , dalam hal ini panggilan 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()

Gunakan kode asinkron seolah-olah sinkron :

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

Menggunakan Janji

Jawaban paling sempurna untuk pertanyaan ini adalah menggunakan 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);
  });
}

Pemakaian

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

Tapi tunggu...!

Ada masalah dengan menggunakan janji!

Mengapa kita harus menggunakan Janji kebiasaan kita sendiri?

Saya menggunakan solusi ini untuk sementara waktu sampai saya menemukan ada kesalahan di browser lama:

Uncaught ReferenceError: Promise is not defined

Jadi saya memutuskan untuk mengimplementasikan kelas Promise saya sendiri untuk ES3 di bawah kompiler js jika tidak ditentukan. Cukup tambahkan kode ini sebelum kode utama Anda dan kemudian gunakan Promise dengan aman!

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;
    }();
}
Amir Fo
sumber
28

Tentu saja ada banyak pendekatan seperti permintaan sinkron, janji, tetapi dari pengalaman saya, saya pikir Anda harus menggunakan pendekatan callback. Itu wajar untuk perilaku Javascript asinkron. Jadi, cuplikan kode Anda dapat ditulis ulang sedikit berbeda:

function foo() {
    var result;

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

    return result;
}

function myCallback(response) {
    // Does something.
}
Khoa Bui
sumber
5
Tidak ada yang secara inheren tidak sinkron tentang panggilan balik atau JavaScript.
Aluan Haddad
19

Daripada melempar kode pada Anda, ada 2 konsep yang merupakan kunci untuk memahami bagaimana JS menangani panggilan balik dan asinkronisitas. (Apakah itu sepatah kata pun?)

Model Lingkaran Acara dan Konkurensi

Ada tiga hal yang perlu Anda waspadai; Antrian; loop acara dan tumpukan

Secara luas, istilah sederhana, loop acara seperti manajer proyek, itu terus-menerus mendengarkan fungsi yang ingin dijalankan dan berkomunikasi antara antrian dan tumpukan.

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

Setelah menerima pesan untuk menjalankan sesuatu, ia menambahkannya ke antrian. Antrian adalah daftar hal-hal yang menunggu untuk dieksekusi (seperti permintaan AJAX Anda). bayangkan seperti ini:

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

Ketika salah satu dari pesan ini akan mengeksekusinya muncul pesan dari antrian dan membuat stack, stack adalah segalanya yang perlu dieksekusi oleh JS untuk melakukan instruksi dalam pesan. Jadi, dalam contoh kita, disuruh meneleponfoobarFunc

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

Jadi apa pun yang perlu dieksekusi foobarFunc (dalam kasus kami anotherFunction) akan didorong ke stack. dieksekusi, dan kemudian dilupakan - loop acara kemudian akan pindah ke hal berikutnya dalam antrian (atau mendengarkan pesan)

Kuncinya di sini adalah urutan eksekusi. Itu adalah

KAPAN sesuatu akan berjalan

Saat Anda melakukan panggilan menggunakan AJAX ke pihak eksternal atau menjalankan kode asinkron apa pun (misalnya, setTimeout), Javascript bergantung pada respons sebelum dapat melanjutkan.

Pertanyaan besarnya adalah kapan ia akan mendapat respons? Jawabannya adalah kita tidak tahu - jadi loop acara sedang menunggu pesan untuk mengatakan "hey jalankan saya". Jika JS hanya menunggu pesan itu secara sinkron, aplikasi Anda akan membeku dan akan menyedot. Jadi JS melakukan eksekusi item berikutnya dalam antrian sambil menunggu pesan untuk ditambahkan kembali ke antrian.

Itu sebabnya dengan fungsionalitas asinkron kami menggunakan hal-hal yang disebut panggilan balik . Agak seperti janji secara harfiah. Seperti dalam Saya berjanji untuk mengembalikan sesuatu di beberapa titik jQuery menggunakan panggilan balik khusus yang disebut deffered.done deffered.faildan deffered.always(antara lain). Anda dapat melihat semuanya di sini

Jadi yang perlu Anda lakukan adalah melewatkan fungsi yang dijanjikan untuk dieksekusi di beberapa titik dengan data yang diteruskan ke sana.

Karena panggilan balik tidak dieksekusi segera tetapi di lain waktu penting untuk meneruskan referensi ke fungsi yang tidak dieksekusi. begitu

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

sehingga sebagian besar waktu (tetapi tidak selalu) Anda akan melewati footidakfoo()

Semoga itu masuk akal. Ketika Anda menemukan hal-hal seperti ini yang membingungkan - saya sangat merekomendasikan membaca dokumentasi sepenuhnya untuk setidaknya mendapatkan pemahaman tentang itu. Ini akan membuat Anda menjadi pengembang yang jauh lebih baik.

Matthew Brent
sumber
18

Menggunakan ES2017 Anda harus memiliki ini sebagai deklarasi fungsi

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

Dan menjalankannya seperti ini.

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

Atau sintaksis Janji

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

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

})
Fernando Carvajal
sumber
dapatkah fungsi kedua digunakan kembali ??
Zum Dummi
Bagaimana Anda menggunakan hasilnya jika oncolse, log dipanggil? Bukankah semuanya hanya masuk ke konsol pada saat itu?
Ken Ingram