Bagaimana menghindari bersarangnya fungsi asinkron dalam Node.js

158

Saya ingin membuat halaman yang menampilkan beberapa data dari DB, jadi saya telah membuat beberapa fungsi yang mendapatkan data dari DB saya. Saya hanya seorang pemula di Node.js, sejauh yang saya mengerti, jika saya ingin menggunakan semuanya dalam satu halaman (respon HTTP), saya harus membuat semuanya:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

Jika ada banyak fungsi seperti itu, maka persarangan menjadi masalah .

Apakah ada cara untuk menghindari ini? Saya kira itu ada hubungannya dengan bagaimana Anda menggabungkan beberapa fungsi asinkron, yang tampaknya menjadi sesuatu yang mendasar.

Kay Pale
sumber
12
Jadi ketika Anda memiliki 10 fungsi async, Anda memiliki 10 level indentasi?
Kay Pale
Tautan ini dapat membantu. stackoverflow.com/a/4631909/290340
Evan Plaice
1
Masalah lain: menyisipkan fungsi lain di antara getSomeDatedan getSomeOtherDateberakhir dengan mengubah lekukan banyak baris yang membuat sejarah git lebih sulit dibaca ( git blamebahkan tidak berguna setelah ini), dan Anda mungkin membuat bug saat melakukan ini secara manual
Daniel Alder

Jawaban:

73

Pengamatan menarik. Perhatikan bahwa dalam JavaScript Anda biasanya dapat mengganti fungsi panggilan balik anonim sebaris dengan variabel fungsi bernama.

Pengikut:

http.createServer(function (req, res) {
   // inline callback function ...

   getSomeData(client, function (someData) {
      // another inline callback function ...

      getMoreData(client, function(moreData) {
         // one more inline callback function ...
      });
   });

   // etc ...
});

Dapat ditulis ulang agar terlihat seperti ini:

var moreDataParser = function (moreData) {
   // date parsing logic
};

var someDataParser = function (someData) {
   // some data parsing logic

   getMoreData(client, moreDataParser);
};

var createServerCallback = function (req, res) {
   // create server logic

   getSomeData(client, someDataParser);

   // etc ...
};

http.createServer(createServerCallback);

Namun, kecuali jika Anda berencana untuk menggunakan kembali logika panggilan balik di tempat lain, seringkali lebih mudah untuk membaca fungsi anonim sebaris, seperti pada contoh Anda. Ini juga akan menghindarkan Anda dari keharusan mencari nama untuk semua panggilan balik.

Sebagai tambahan perhatikan bahwa seperti @pst dicatat dalam komentar di bawah, jika Anda mengakses variabel penutupan dalam fungsi-fungsi dalam, di atas tidak akan menjadi terjemahan langsung. Dalam kasus seperti itu, menggunakan fungsi anonim sebaris bahkan lebih disukai.

Daniel Vassallo
sumber
26
Namun, (dan benar-benar hanya untuk memahami pertukaran) ketika tidak tersarang, beberapa semantik penutupan atas variabel dapat hilang sehingga itu bukan terjemahan langsung. Dalam contoh di atas, akses ke 'res' in getMoreDatahilang.
2
Saya pikir solusi Anda rusak: someDataParsersebenarnya mem-parsing SEMUA data, karena itu juga memanggil getMoreData. Dalam pengertian itu, nama fungsi salah dan menjadi jelas bahwa kita belum benar-benar menghapus masalah bersarang.
Konstantin Schubert
63

Kay, cukup gunakan salah satu modul ini.

Ini akan mengubah ini:

dbGet('userIdOf:bobvance', function(userId) {
    dbSet('user:' + userId + ':email', '[email protected]', function() {
        dbSet('user:' + userId + ':firstName', 'Bob', function() {
            dbSet('user:' + userId + ':lastName', 'Vance', function() {
                okWeAreDone();
            });
        });
    });
});

Ke dalam ini:

flow.exec(
    function() {
        dbGet('userIdOf:bobvance', this);

    },function(userId) {
        dbSet('user:' + userId + ':email', '[email protected]', this.MULTI());
        dbSet('user:' + userId + ':firstName', 'Bob', this.MULTI());
        dbSet('user:' + userId + ':lastName', 'Vance', this.MULTI());

    },function() {
        okWeAreDone()
    }
);
Baggz
sumber
9
Melihat sekilas pada flow-js, step dan async dan tampaknya mereka hanya berurusan dengan urutan eksekusi fungsi. Dalam kasus saya ada akses ke variabel penutupan inline di setiap indentasi. Jadi misalnya fungsi berfungsi seperti ini: dapatkan HTTP req / res, dapatkan userid dari DB untuk cookie, dapatkan email untuk userid nanti, dapatkan lebih banyak data untuk email selanjutnya, ..., dapatkan X untuk nanti Y, ... Jika saya tidak salah, kerangka kerja ini hanya memastikan bahwa fungsi async akan dieksekusi dalam urutan yang benar, tetapi di setiap fungsi tubuh tidak ada cara untuk mendapatkan variabel yang disediakan secara alami oleh penutupan (?) Terima kasih :)
Kay Pale
9
Dalam hal peringkat perpustakaan-perpustakaan ini, saya memeriksa jumlah "Bintang" pada masing-masing di Github. async memiliki yang terbanyak dengan sekitar 3000, Langkah berikutnya dengan sekitar 1000, yang lainnya secara signifikan lebih sedikit. Tentu saja, mereka tidak semua melakukan hal yang sama :-)
kgilpin
3
@KayPale Saya cenderung menggunakan async.waterfall, dan kadang-kadang akan memiliki fungsi saya sendiri untuk setiap tahap / langkah yang akan melewati apa yang dibutuhkan langkah berikutnya, atau menentukan variabel sebelum panggilan async.METHOD sehingga tersedia downline. Juga akan menggunakan METHODNAME.bind (...) untuk panggilan async * saya, yang bekerja dengan cukup baik.
Pelacak1
Pertanyaan singkat: Dalam daftar modul Anda, apakah dua yang terakhir sama? Yaitu "async.js" dan "async"
dari0h
18

Sebagian besar, saya setuju dengan Daniel Vassallo. Jika Anda dapat memecah fungsi yang rumit dan bersarang menjadi fungsi bernama terpisah, maka itu biasanya ide yang baik. Untuk saat-saat ketika masuk akal untuk melakukannya di dalam satu fungsi, Anda dapat menggunakan salah satu dari banyak pustaka async node.js yang tersedia. Orang-orang telah menemukan banyak cara berbeda untuk mengatasi hal ini, jadi lihat halaman modul node.js dan lihat apa yang Anda pikirkan.

Saya telah menulis modul untuk ini sendiri, bernama async.js . Dengan menggunakan ini, contoh di atas dapat diperbarui ke:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  async.series({
    someData: async.apply(getSomeDate, client),
    someOtherData: async.apply(getSomeOtherDate, client),
    moreData: async.apply(getMoreData, client)
  },
  function (err, results) {
    var html = "<h1>Demo page</h1>";
    html += "<p>" + results.someData + "</p>";
    html += "<p>" + results.someOtherData + "</p>";
    html += "<p>" + results.moreData + "</p>";
    res.write(html);
    res.end();
  });
});

Satu hal yang menyenangkan tentang pendekatan ini adalah Anda dapat dengan cepat mengubah kode Anda untuk mengambil data secara paralel dengan mengubah fungsi 'seri' menjadi 'paralel'. Terlebih lagi, async.js juga akan berfungsi di dalam browser, sehingga Anda dapat menggunakan metode yang sama seperti di node.js jika Anda menemukan kode async yang rumit.

Semoga bermanfaat!

Caolan
sumber
Hai Caolan dan terima kasih atas jawabannya! Dalam kasus saya ada akses ke variabel penutupan inline di setiap indentasi. Jadi misalnya fungsi berfungsi seperti ini: dapatkan HTTP req / res, dapatkan userid dari DB untuk cookie, dapatkan email untuk userid nanti, dapatkan lebih banyak data untuk email selanjutnya, ..., dapatkan X untuk nanti Y, ... Jika saya tidak salah, kode yang Anda sarankan hanya memastikan bahwa fungsi async akan dieksekusi dalam urutan yang benar, tetapi di setiap fungsi tubuh tidak ada cara untuk mendapatkan variabel yang disediakan secara alami oleh penutupan di kode asli saya. Apakah itu masalahnya?
Kay Pale
3
Apa yang ingin Anda capai secara arsitektur disebut jalur pipa data. Anda dapat menggunakan air terjun async untuk kasus-kasus seperti itu.
Rudolf Meijering
18

Anda bisa menggunakan trik ini dengan array daripada fungsi bersarang atau modul.

Jauh lebih mudah di mata.

var fs = require("fs");
var chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step3");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step4");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("step5");
        fs.stat("f2.js",chain.shift());
    },
    function(err, stats) {
        console.log("done");
    },
];
chain.shift()();

Anda dapat memperluas idiom untuk proses paralel atau bahkan rantai proses paralel:

var fs = require("fs");
var fork1 = 2, fork2 = 2, chain = [
    function() { 
        console.log("step1");
        fs.stat("f1.js",chain.shift());
    },
    function(err, stats) {
        console.log("step2");
        var next = chain.shift();
        fs.stat("f2a.js",next);
        fs.stat("f2b.js",next);
    },
    function(err, stats) {
        if ( --fork1 )
            return;
        console.log("step3");
        var next = chain.shift();

        var chain1 = [
            function() { 
                console.log("step4aa");
                fs.stat("f1.js",chain1.shift());
            },
            function(err, stats) { 
                console.log("step4ab");
                fs.stat("f1ab.js",next);
            },
        ];
        chain1.shift()();

        var chain2 = [
            function() { 
                console.log("step4ba");
                fs.stat("f1.js",chain2.shift());
            },
            function(err, stats) { 
                console.log("step4bb");
                fs.stat("f1ab.js",next);
            },
        ];
        chain2.shift()();
    },
    function(err, stats) {
        if ( --fork2 )
            return;
        console.log("done");
    },
];
chain.shift()();
Guido
sumber
15

Saya suka async.js banyak untuk tujuan ini.

Masalah ini diselesaikan dengan perintah air terjun:

air terjun (tugas, [panggilan balik])

Menjalankan array fungsi secara seri, masing-masing meneruskan hasilnya ke array berikutnya. Namun, jika ada fungsi yang mengirimkan kesalahan ke callback, fungsi berikutnya tidak dieksekusi dan panggilan balik utama segera dipanggil dengan kesalahan tersebut.

Argumen

tugas - Array fungsi yang harus dijalankan, setiap fungsi dilewati panggilan balik (err, result1, result2, ...) yang harus dipanggil saat selesai. Argumen pertama adalah kesalahan (yang bisa nol) dan argumen lebih lanjut akan diteruskan sebagai argumen untuk tugas berikutnya. callback (err, [results]) - Callback opsional untuk dijalankan setelah semua fungsi selesai. Ini akan melewati hasil panggilan balik tugas terakhir.

Contoh

async.waterfall([
    function(callback){
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback){
        callback(null, 'three');
    },
    function(arg1, callback){
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
    // result now equals 'done'    
});

Adapun req, variabel res, mereka akan dibagi dalam lingkup yang sama dengan fungsi (req, res) {} yang melampirkan seluruh panggilan async.waterfall.

Tidak hanya itu, async juga sangat bersih. Maksud saya adalah saya mengubah banyak kasus seperti ini:

function(o,cb){
    function2(o,function(err, resp){
        cb(err,resp);
    })
}

Untuk pertama:

function(o,cb){
    function2(o,cb);
}

Kemudian untuk ini:

function2(o,cb);

Kemudian untuk ini:

async.waterfall([function2,function3,function4],optionalcb)

Ini juga memungkinkan banyak fungsi premade yang disiapkan untuk async dipanggil dari util.js dengan sangat cepat. Hanya rantai apa yang ingin Anda lakukan, pastikan o, cb ditangani secara universal. Ini mempercepat seluruh proses pengkodean.

Grant Li
sumber
11

Yang Anda butuhkan adalah sedikit gula sintaksis. Hapus ini:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = ["<h1>Demo page</h1>"];
  var pushHTML = html.push.bind(html);

  Queue.push( getSomeData.partial(client, pushHTML) );
  Queue.push( getSomeOtherData.partial(client, pushHTML) );
  Queue.push( getMoreData.partial(client, pushHTML) );
  Queue.push( function() {
    res.write(html.join(''));
    res.end();
  });
  Queue.execute();
}); 

Cukup rapi , bukan? Anda mungkin memperhatikan bahwa html menjadi sebuah array. Itu sebagian karena string tidak dapat diubah, jadi Anda lebih baik dengan buffering output Anda dalam array, daripada membuang string yang lebih besar dan lebih besar. Alasan lainnya adalah karena sintaks yang bagus dengan bind.

Queuedalam contoh tersebut benar-benar hanya sebuah contoh dan bersamaan dengan itu partialdapat diimplementasikan sebagai berikut

// Functional programming for the rescue
Function.prototype.partial = function() {
  var fun = this,
      preArgs = Array.prototype.slice.call(arguments);
  return function() {
    fun.apply(null, preArgs.concat.apply(preArgs, arguments));
  };
};

Queue = [];
Queue.execute = function () {
  if (Queue.length) {
    Queue.shift()(Queue.execute);
  }
};
gblazex
sumber
1
Queue.execute () hanya akan menjalankan parsial satu demi satu, tanpa menunggu hasil dari panggilan async.
ngn
Temukan, terima kasih. Saya sudah memperbarui jawabannya. Berikut ini adalah tesnya: jsbin.com/ebobo5/edit (dengan lastfungsi opsional )
gblazex
Hai galambalaz dan terima kasih atas jawabannya! Dalam kasus saya ada akses ke variabel penutupan inline di setiap indentasi. Jadi misalnya fungsi berfungsi seperti ini: dapatkan HTTP req / res, dapatkan userid dari DB untuk cookie, dapatkan email untuk userid nanti, dapatkan lebih banyak data untuk email selanjutnya, ..., dapatkan X untuk nanti Y, ... Jika saya tidak salah, kode yang Anda sarankan hanya memastikan bahwa fungsi async akan dieksekusi dalam urutan yang benar, tetapi di setiap fungsi tubuh tidak ada cara untuk mendapatkan variabel yang disediakan secara alami oleh penutupan di kode asli saya. Apakah itu masalahnya?
Kay Pale
1
Yah Anda pasti kehilangan penutupan dalam semua jawaban. Yang dapat Anda lakukan adalah membuat objek dalam lingkup global untuk data bersama . Jadi mis. Fungsi pertama Anda ditambahkan obj.emaildan fungsi Anda berikutnya menggunakan obj.emaillalu menghapusnya (atau hanya menetapkan null).
gblazex
7

Aku jatuh cinta pada Async.js sejak aku menemukannya. Ini memiliki async.seriesfungsi yang dapat Anda gunakan untuk menghindari lama bersarang.

Dokumentasi:-


seri (tugas, [panggilan balik])

Jalankan berbagai fungsi secara seri, masing-masing berjalan setelah fungsi sebelumnya selesai. [...]

Argumen

tasks- Berbagai fungsi untuk dijalankan, setiap fungsi dilewati panggilan balik yang harus dipanggil saat selesai. callback(err, [results])- Callback opsional untuk dijalankan setelah semua fungsi selesai. Fungsi ini mendapatkan array dari semua argumen yang diteruskan ke panggilan balik yang digunakan dalam array.


Begini cara kami menerapkannya pada kode contoh Anda: -

http.createServer(function (req, res) {

    res.writeHead(200, {'Content-Type': 'text/html'});

    var html = "<h1>Demo page</h1>";

    async.series([
        function (callback) {
            getSomeData(client, function (someData) { 
                html += "<p>"+ someData +"</p>";

                callback();
            });
        },

        function (callback) {
            getSomeOtherData(client, function (someOtherData) { 
                html += "<p>"+ someOtherData +"</p>";

                callback(); 
            });
        },

        funciton (callback) {
            getMoreData(client, function (moreData) {
                html += "<p>"+ moreData +"</p>";

                callback();
            });
        }
    ], function () {
        res.write(html);
        res.end();
    });
});
Salman von Abbas
sumber
6

Gula sintaksis paling sederhana yang saya lihat adalah simpul-janji.

npm instal simpul-janji || git clone https://github.com/kriszyp/node-promise

Dengan ini, Anda dapat menggunakan metode async sebagai:

firstMethod().then(secondMethod).then(thirdMethod);

Nilai kembali dari masing-masing tersedia sebagai argumen di berikutnya.

Nikhil Ranjan
sumber
3

Apa yang telah Anda lakukan di sana adalah mengambil pola asynch dan menerapkannya pada 3 fungsi yang disebut secara berurutan, masing-masing menunggu yang sebelumnya selesai sebelum mulai - yaitu Anda telah membuatnya sinkron . Intinya tentang pemrograman asynch adalah Anda dapat memiliki beberapa fungsi yang semuanya berjalan sekaligus dan tidak harus menunggu masing-masing untuk menyelesaikan.

jika getSomeDate () tidak menyediakan apa pun untuk getSomeOtherDate (), yang tidak menyediakan apa pun untuk getMoreData () lalu mengapa Anda tidak memanggilnya secara asinkron sesuai js memungkinkan atau jika mereka saling bergantung (dan tidak asinkron) tuliskan mereka sebagai fungsi tunggal?

Anda tidak perlu menggunakan sarang untuk mengontrol aliran - misalnya, dapatkan setiap fungsi selesai dengan memanggil fungsi umum yang menentukan kapan ketiganya telah selesai dan kemudian mengirim respons.

Nick Tulett
sumber
2

Misalkan Anda bisa melakukan ini:

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    var html = "<h1>Demo page</h1>";
    chain([
        function (next) {
            getSomeDate(client, next);
        },
        function (next, someData) {
            html += "<p>"+ someData +"</p>";
            getSomeOtherDate(client, next);
        },
        function (next, someOtherData) {
            html += "<p>"+ someOtherData +"</p>";
            getMoreData(client, next);
        },
        function (next, moreData) {
            html += "<p>"+ moreData +"</p>";
            res.write(html);
            res.end();
        }
    ]);
});

Anda hanya perlu mengimplementasikan rantai () sehingga sebagian menerapkan setiap fungsi ke yang berikutnya, dan segera memanggil hanya fungsi pertama:

function chain(fs) {
    var f = function () {};
    for (var i = fs.length - 1; i >= 0; i--) {
        f = fs[i].partial(f);
    }
    f();
}
ngn
sumber
Hai ngn dan terima kasih atas jawabannya! Dalam kasus saya ada akses ke variabel penutupan inline di setiap indentasi. Jadi misalnya fungsi berfungsi seperti ini: dapatkan HTTP req / res, dapatkan userid dari DB untuk cookie, dapatkan email untuk userid nanti, dapatkan lebih banyak data untuk email selanjutnya, ..., dapatkan X untuk nanti Y, ... Jika saya tidak salah, kode yang Anda sarankan hanya memastikan bahwa fungsi async akan dieksekusi dalam urutan yang benar, tetapi di setiap fungsi tubuh tidak ada cara untuk mendapatkan variabel yang disediakan secara alami oleh penutupan di kode asli saya. Apakah itu masalahnya?
Kay Pale
2

panggilan balik neraka dapat dengan mudah dihindari dalam javascript murni dengan penutupan. solusi di bawah ini mengasumsikan semua panggilan balik mengikuti tanda tangan fungsi (kesalahan, data).

http.createServer(function (req, res) {
  var modeNext, onNext;

  // closure variable to keep track of next-callback-state
  modeNext = 0;

  // next-callback-handler
  onNext = function (error, data) {
    if (error) {
      modeNext = Infinity;
    } else {
      modeNext += 1;
    }
    switch (modeNext) {

    case 0:
      res.writeHead(200, {'Content-Type': 'text/html'});
      var html = "<h1>Demo page</h1>";
      getSomeDate(client, onNext);
      break;

    // handle someData
    case 1:
        html += "<p>"+ data +"</p>";
        getSomeOtherDate(client, onNext);
        break;

    // handle someOtherData
    case 2:
      html += "<p>"+ data +"</p>";
      getMoreData(client, onNext);
      break;

    // handle moreData
    case 3:
      html += "<p>"+ data +"</p>";
      res.write(html);
      res.end();
      break;

    // general catch-all error-handler
    default:
      res.statusCode = 500;
      res.end(error.message + '\n' + error.stack);
    }
  };
  onNext();
});
kai zhu
sumber
1

Saya baru-baru ini membuat abstraksi sederhana bernama wait.for untuk memanggil fungsi async dalam mode sinkronisasi (berdasarkan Serat). Ini pada tahap awal tetapi berhasil. Ini di:

https://github.com/luciotato/waitfor

Menggunakan wait.for , Anda dapat memanggil fungsi async nodejs standar apa saja, seolah-olah itu adalah fungsi sinkronisasi.

menggunakan wait.for kode Anda bisa:

var http=require('http');
var wait=require('wait.for');

http.createServer(function(req, res) {
  wait.launchFiber(handleRequest,req, res); //run in a Fiber, keep node spinning
}).listen(8080);


//in a fiber
function handleRequest(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  var someData = wait.for(getSomeDate,client);
  html += "<p>"+ someData +"</p>";
  var someOtherData = wait.for(getSomeOtherDate,client);
  html += "<p>"+ someOtherData +"</p>";
  var moreData = wait.for(getMoreData,client);
  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
};

... atau jika Anda ingin kurang verbose (dan juga menambahkan kesalahan menangkap)

//in a fiber
function handleRequest(req, res) {
  try {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(
    "<h1>Demo page</h1>" 
    + "<p>"+ wait.for(getSomeDate,client) +"</p>"
    + "<p>"+ wait.for(getSomeOtherDate,client) +"</p>"
    + "<p>"+ wait.for(getMoreData,client) +"</p>"
    );
    res.end();
  }
  catch(err) {
   res.end('error '+e.message); 
  }

};

Dalam semua kasus, getSomeDate , getSomeOtherDate dan getMoreData harus menjadi fungsi async standar dengan parameter terakhir berupa fungsi callback (err, data)

seperti dalam:

function getMoreData(client, callback){
  db.execute('select moredata from thedata where client_id=?',[client.id],
       ,function(err,data){
          if (err) callback(err);
          callback (null,data);
        });
}
Lucio M. Tato
sumber
1

Untuk mengatasi masalah ini saya menulis anggukan ( https://npmjs.org/package/nodent ) yang secara tak terlihat memproses pra-JS Anda. Kode contoh Anda akan menjadi (async, really - read the docs).

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  someData <<= getSomeDate(client) ;

  html += "<p>"+ someData +"</p>";
  someOtherData <<= getSomeOtherDate(client) ;

  html += "<p>"+ someOtherData +"</p>";
  moreData <<= getMoreData(client) ;

  html += "<p>"+ moreData +"</p>";
  res.write(html);
  res.end();
});

Jelas, ada banyak solusi lain, tetapi pra-pemrosesan memiliki keuntungan memiliki sedikit overhead atau tidak ada run-time dan berkat dukungan sumber-peta juga mudah untuk debug.

MatAtBread
sumber
0

Saya memiliki masalah yang sama. Saya telah melihat libs utama untuk menjalankan fungsi async node, dan mereka menyajikan chaining begitu tidak alami (Anda perlu menggunakan tiga atau lebih metode confs dll) untuk membangun kode Anda.

Saya menghabiskan beberapa minggu mengembangkan solusi untuk menjadi sederhana dan mudah dibaca. Tolong, coba untuk EnqJS . Semua pendapat akan dihargai.

Dari pada:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";
  getSomeDate(client, function(someData) {
    html += "<p>"+ someData +"</p>";
    getSomeOtherDate(client, function(someOtherData) {
      html += "<p>"+ someOtherData +"</p>";
      getMoreData(client, function(moreData) {
        html += "<p>"+ moreData +"</p>";
        res.write(html);
        res.end();
      });
    });
  });

dengan EnqJS:

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  var html = "<h1>Demo page</h1>";

  enq(function(){
    var self=this;
    getSomeDate(client, function(someData){
      html += "<p>"+ someData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getSomeOtherDate(client, function(someOtherData){ 
      html += "<p>"+ someOtherData +"</p>";
      self.return();
    })
  })(function(){
    var self=this;
    getMoreData(client, function(moreData) {
      html += "<p>"+ moreData +"</p>";
      self.return();
      res.write(html);
      res.end();
    });
  });
});

Perhatikan bahwa kode tersebut tampak lebih besar dari sebelumnya. Tapi itu tidak bersarang seperti sebelumnya. Agar tampak lebih alami, rantai disebut segera:

enq(fn1)(fn2)(fn3)(fn4)(fn4)(...)

Dan untuk mengatakan bahwa itu kembali, di dalam fungsi yang kami sebut:

this.return(response)
Thadeu de Paula
sumber
0

Saya melakukannya dengan cara yang cukup primitif tetapi efektif. Misalnya saya perlu mendapatkan model dengan orang tua dan anak-anak dan katakanlah saya perlu melakukan pertanyaan terpisah untuk mereka:

var getWithParents = function(id, next) {
  var getChildren = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      },
      getParents = function(model, next) {
        /*... code ... */
        return next.pop()(model, next);
      }
      getModel = function(id, next) {
        /*... code ... */
        if (model) {
          // return next callbacl
          return next.pop()(model, next);
        } else {
          // return last callback
          return next.shift()(null, next);
        }
      }

  return getModel(id, [getParents, getChildren, next]);
}
mvbl fst
sumber
0

Gunakan Serat https://github.com/laverdet/node-fibers membuat kode asinkron terlihat seperti sinkron (tanpa pemblokiran)

Saya pribadi menggunakan sedikit pembungkus http://alexeypetrushin.github.com/synchronize Contoh kode dari proyek saya (setiap metode sebenarnya asynchronous, bekerja dengan file yang async IO) Aku bahkan takut membayangkan apa berantakan itu akan dengan callback atau perpustakaan pembantu async-control-flow.

_update: (version, changesBasePath, changes, oldSite) ->
  @log 'updating...'
  @_updateIndex version, changes
  @_updateFiles version, changesBasePath, changes
  @_updateFilesIndexes version, changes
  configChanged = @_updateConfig version, changes
  @_updateModules version, changes, oldSite, configChanged
  @_saveIndex version
  @log "updated to #{version} version"
Alexey Petrushin
sumber
0

Task.js menawarkan ini kepada Anda:

spawn(function*() {
    try {
        var [foo, bar] = yield join(read("foo.json"),
                                    read("bar.json")).timeout(1000);
        render(foo);
        render(bar);
    } catch (e) {
        console.log("read failed: " + e);
    }
});

Alih-alih ini:

var foo, bar;
var tid = setTimeout(function() { failure(new Error("timed out")) }, 1000);

var xhr1 = makeXHR("foo.json",
                   function(txt) { foo = txt; success() },
                   function(err) { failure() });
var xhr2 = makeXHR("bar.json",
                   function(txt) { bar = txt; success() },
                   function(e) { failure(e) });

function success() {
    if (typeof foo === "string" && typeof bar === "string") {
        cancelTimeout(tid);
        xhr1 = xhr2 = null;
        render(foo);
        render(bar);
    }
}

function failure(e) {
    xhr1 && xhr1.abort();
    xhr1 = null;
    xhr2 && xhr2.abort();
    xhr2 = null;
    console.log("read failed: " + e);
}
Janus Troelsen
sumber
0

Setelah yang lain merespons, Anda menyatakan bahwa masalah Anda adalah variabel lokal. Tampaknya cara yang mudah untuk melakukan ini adalah menulis satu fungsi luar untuk memuat variabel-variabel lokal, kemudian menggunakan sekelompok fungsi dalam bernama dan mengaksesnya dengan nama. Dengan cara ini, Anda hanya akan memiliki dua sarang, terlepas dari berapa banyak fungsi yang Anda butuhkan untuk rantai bersama.

Ini adalah usaha pemula saya untuk menggunakan mysql modul Node.js dengan nesting:

function with_connection(sql, bindings, cb) {
    pool.getConnection(function(err, conn) {
        if (err) {
            console.log("Error in with_connection (getConnection): " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, function(err, results) {
            if (err) {
                console.log("Error in with_connection (query): " + JSON.stringify(err));
                cb(true);
                return;
            }
            console.log("with_connection results: " + JSON.stringify(results));
            cb(false, results);
        });
    });
}

Berikut ini adalah penulisan ulang menggunakan fungsi dalam bernama. Fungsi luar with_connectionjuga dapat digunakan sebagai penahan untuk variabel lokal. (Di sini, saya punya parameter sql, bindings,cb yang bertindak dengan cara yang sama, tetapi Anda hanya dapat mendefinisikan beberapa variabel lokal tambahan di with_connection.)

function with_connection(sql, bindings, cb) {

    function getConnectionCb(err, conn) {
        if (err) {
            console.log("Error in with_connection/getConnectionCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        conn.query(sql, bindings, queryCb);
    }

    function queryCb(err, results) {
        if (err) {
            console.log("Error in with_connection/queryCb: " + JSON.stringify(err));
            cb(true);
            return;
        }
        cb(false, results);
    }

    pool.getConnection(getConnectionCb);
}

Saya telah berpikir bahwa mungkin akan mungkin untuk membuat objek dengan variabel instan, dan menggunakan variabel instan ini sebagai pengganti variabel lokal. Tapi sekarang saya menemukan bahwa pendekatan di atas menggunakan fungsi bersarang dan variabel lokal lebih sederhana dan lebih mudah dipahami. Butuh waktu untuk menghapus OO, sepertinya :-)

Jadi di sini adalah versi saya sebelumnya dengan variabel objek dan instance.

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, function(err, results) { self.query(err, results); });
}
DbConnection.prototype.query = function(err, results) {
    var self = this;
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        self.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    self.cb(false, results);
}

function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    pool.getConnection(function (err, conn) { dbc.getConnection(err, conn); });
}

Ternyata itu bindbisa digunakan untuk beberapa keuntungan. Ini memungkinkan saya untuk menyingkirkan fungsi anonim yang agak jelek yang saya buat yang tidak melakukan banyak hal, kecuali untuk meneruskan diri ke pemanggilan metode. Saya tidak bisa lulus metode secara langsung karena akan terlibat dengan nilai yang salah this. Tetapi dengan bind, saya dapat menentukan nilai thisyang saya inginkan.

function DbConnection(sql, bindings, cb) {
    this.sql = sql;
    this.bindings = bindings;
    this.cb = cb;
}
DbConnection.prototype.getConnection = function(err, conn) {
    var f = this.query.bind(this);
    if (err) {
        console.log("Error in DbConnection.getConnection: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    conn.query(this.sql, this.bindings, f);
}
DbConnection.prototype.query = function(err, results) {
    if (err) {
        console.log("Error in DbConnection.query: " + JSON.stringify(err));
        this.cb(true);
        return;
    }
    console.log("DbConnection results: " + JSON.stringify(results));
    this.cb(false, results);
}

// Get a connection from the pool, execute `sql` in it
// with the given `bindings`.  Invoke `cb(true)` on error,
// invoke `cb(false, results)` on success.  Here,
// `results` is an array of results from the query.
function with_connection(sql, bindings, cb) {
    var dbc = new DbConnection(sql, bindings, cb);
    var f = dbc.getConnection.bind(dbc);
    pool.getConnection(f);
}

Tentu saja, semua ini bukan JS yang sesuai dengan Node.js coding - Saya hanya menghabiskan beberapa jam untuk itu. Tapi mungkin dengan sedikit memoles teknik ini bisa membantu?

hibbelig
sumber
0

Jika Anda tidak ingin menggunakan "step" atau "seq", silakan coba "line" yang merupakan fungsi sederhana untuk mengurangi callback async bersarang.

https://github.com/kevin0571/node-line

Kevin
sumber
0

Asyncawait mirip C adalah cara lain untuk melakukan ini

https://github.com/yortus/asyncawait

async(function(){

    var foo = await(bar());
    var foo2 = await(bar2());
    var foo3 = await(bar2());

}
Artur Stary
sumber
0

Menggunakan kawat kode Anda akan terlihat seperti ini:

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});

    var l = new Wire();

    getSomeDate(client, l.branch('someData'));
    getSomeOtherDate(client, l.branch('someOtherData'));
    getMoreData(client, l.branch('moreData'));

    l.success(function(r) {
        res.write("<h1>Demo page</h1>"+
            "<p>"+ r['someData'] +"</p>"+
            "<p>"+ r['someOtherData'] +"</p>"+
            "<p>"+ r['moreData'] +"</p>");
        res.end();
    });
});
Daniel Garmoshka
sumber
0

untuk Anda ketahui pertimbangkan Jazz.js https://github.com/Javanile/Jazz.js/wiki/Script-showcase

    const jj = membutuhkan ('jazz.js');

    // tumpukan ultra-compat
    jj.script ([
        a => ProcessTaskOneCallbackAtEnd (a),
        b => ProcessTaskTwoCallbackAtEnd (b),
        c => ProcessTaskThreeCallbackAtEnd (c),
        d => ProcessTaskFourCallbackAtEnd (d),
        e => ProcessTaskFiveCallbackAtEnd (e),
    ]);

cicciodarkast
sumber