Berurusan dengan pyramid callback node.js

9

Saya baru saja mulai menggunakan node, dan satu hal yang saya perhatikan dengan cepat adalah seberapa cepat callback dapat membangun ke tingkat indentasi yang konyol:

doStuff(arg1, arg2, function(err, result) {
    doMoreStuff(arg3, arg4, function(err, result) {
        doEvenMoreStuff(arg5, arg6, function(err, result) {
            omgHowDidIGetHere();
        });
    });
});

The panduan gaya resmi mengatakan untuk menempatkan setiap panggilan balik dalam fungsi yang terpisah, tapi itu tampaknya terlalu ketat pada penggunaan penutupan, dan membuat satu objek dideklarasikan di tingkat atas tersedia beberapa lapisan bawah, sebagai objek tersebut harus melewati semua panggilan balik menengah.

Apakah boleh menggunakan lingkup fungsi untuk membantu di sini? Letakkan semua fungsi panggilan balik yang membutuhkan akses ke objek global di dalam fungsi yang menyatakan objek itu, jadi ia akan ditutup?

function topLevelFunction(globalishObject, callback) {

    function doMoreStuffImpl(err, result) {
        doMoreStuff(arg5, arg6, function(err, result) {
            callback(null, globalishObject);
        });
    }

    doStuff(arg1, arg2, doMoreStuffImpl);
}

dan seterusnya untuk beberapa lapisan lagi ...

Atau apakah ada kerangka kerja dll untuk membantu mengurangi tingkat indentasi tanpa mendeklarasikan fungsi yang disebutkan untuk setiap panggilan balik tunggal? Bagaimana Anda menangani piramida callback?

thecoop
sumber
2
Catatan masalah tambahan dengan penutupan - dalam penutupan JS menangkap seluruh konteks induk (dalam bahasa lain hanya menutup variabel yang digunakan atau yang diminta pengguna secara khusus) menyebabkan beberapa kebocoran memori yang bagus jika hirarki panggil balik cukup dalam dan jika, misalnya, panggilan balik dipertahankan di suatu tempat.
Eugene

Jawaban:

7

Janji memberikan pemisahan yang bersih antara perilaku asinkron dan antarmuka sehingga fungsi asinkron dapat dipanggil tanpa panggilan balik, dan interaksi panggilan balik dapat dilakukan pada antarmuka janji umum.

Ada beberapa implementasi "janji":


Misalnya, Anda dapat menulis ulang callback bersarang ini

http.get(url.parse("http://test.com/"), function(res) {
    console.log(res.statusCode);
    http.get(url.parse(res.headers["location"]), function(res) {
        console.log(res.statusCode);
    });
});

Suka

httpGet(url.parse("http://test.com/")).then(function (res) {
    console.log(res.statusCode);
    return httpGet(url.parse(res.headers["location"]));
}).then(function (res) {
    console.log(res.statusCode);
});

Alih-alih panggilan balik dalam panggilan balik a(b(c()))Anda rantai ".then" a().then(b()).then(c()).


Pengantar di sini: http://howtonode.org/promises

Fabien Sa
sumber
maukah Anda menjelaskan lebih lanjut tentang apa yang dilakukan sumber daya ini dan mengapa Anda merekomendasikan ini sebagai jawaban atas pertanyaan yang diajukan? "Jawaban khusus tautan" tidak diterima di Stack Exchange
agas
1
Ok maaf Saya telah menambahkan contoh dan lebih banyak informasi.
Fabien Sa
3

Sebagai alternatif dari janji-janji Anda harus melihat yieldkata kunci dalam kombinasi dengan fungsi generator yang akan diperkenalkan di EcmaScript 6. Keduanya tersedia hari ini di Node.js 0.11.x builds, tetapi mengharuskan Anda tambahan menentukan --harmonybendera saat menjalankan Node .js:

$ node --harmony app.js

Menggunakan konstruk dan pustaka seperti TJ Holowaychuk's co memungkinkan Anda untuk menulis kode asinkron dengan gaya yang terlihat seperti kode sinkron, meskipun masih berjalan dengan cara yang tidak sinkron. Pada dasarnya, semua ini bersama-sama mengimplementasikan dukungan rutin bersama untuk Node.js.

Pada dasarnya, apa yang perlu Anda lakukan adalah menulis fungsi generator untuk kode yang menjalankan hal-hal asinkron, panggil hal-hal async di sana, tetapi awali dengan yieldkata kunci. Jadi, pada akhirnya kode Anda terlihat seperti:

var run = function * () {
  var data = yield doSomethingAsync();
  console.log(data);
};

Untuk menjalankan fungsi generator ini, Anda memerlukan pustaka seperti co yang disebutkan sebelumnya. Panggilannya kemudian terlihat seperti:

co(run);

Atau, untuk membuatnya sejajar:

co(function * () {
  // ...
});

Harap dicatat bahwa dari fungsi-fungsi generator Anda dapat memanggil fungsi-fungsi generator lainnya, yang perlu Anda lakukan adalah awalan dengan fungsi-fungsi tersebut yieldlagi.

Untuk pengantar topik ini, cari Google untuk istilah seperti yield generators es6 async nodejsdan Anda harus menemukan banyak informasi. Butuh beberapa saat untuk membiasakan diri, tetapi begitu Anda mendapatkannya, Anda tidak ingin kembali lagi.

Harap dicatat bahwa ini tidak hanya menyediakan Anda dengan sintaks yang lebih baik untuk memanggil fungsi, tetapi juga memungkinkan Anda untuk menggunakan hal-hal logika aliran kontrol (sinkron) yang biasa, seperti forloop atau try/ catch. Tidak perlu lagi dipusingkan dengan banyak panggilan balik dan semua hal ini.

Semoga berhasil dan selamat bersenang - senang :-)!

Golo Roden
sumber
0

Anda sekarang memiliki paket asyncawait , dengan sintaks yang sangat dekat dengan apa yang seharusnya menjadi dukungan asli masa depan dari await& asyncdi Node.

Pada dasarnya memungkinkan Anda untuk menulis kode asinkron yang terlihat sinkron , mengurangi LOC & tingkat indentasi secara dramatis, dengan tradeoff kehilangan kinerja yang sedikit (nomor pemilik paket adalah kecepatan 79% dibandingkan dengan panggilan balik mentah), mudah-mudahan berkurang ketika dukungan asli akan tersedia.

Masih merupakan pilihan yang baik untuk keluar dari neraka panggilan balik / piramida malapetaka IMO, ketika kinerja bukan perhatian utama & di mana gaya penulisan sinkron lebih sesuai dengan kebutuhan proyek Anda.

Contoh dasar dari paket doc:

var foo = async (function() {
    var resultA = await (firstAsyncCall());
    var resultB = await (secondAsyncCallUsing(resultA));
    var resultC = await (thirdAsyncCallUsing(resultB));
    return doSomethingWith(resultC);
});
Frosty Z
sumber