Cara termudah untuk menunggu beberapa tugas asinkron selesai, di Javascript?

112

Saya ingin melepaskan beberapa koleksi mongodb, tetapi itu adalah tugas yang tidak sinkron. Kode tersebut adalah:

var mongoose = require('mongoose');

mongoose.connect('mongo://localhost/xxx');

var conn = mongoose.connection;

['aaa','bbb','ccc'].forEach(function(name){
    conn.collection(name).drop(function(err) {
        console.log('dropped');
    });
});
console.log('all dropped');

Konsol menampilkan:

all dropped
dropped
dropped
dropped

Bagaimana cara termudah untuk memastikan all droppedakan dicetak setelah semua koleksi sudah jatuh? Pihak ketiga mana pun dapat digunakan untuk menyederhanakan kode.

Freewind
sumber

Jawaban:

92

Saya melihat Anda menggunakan mongoosejadi Anda berbicara tentang JavaScript sisi server. Dalam hal ini saya menyarankan untuk melihat modul async dan penggunaan async.parallel(...). Anda akan menemukan modul ini sangat membantu - modul ini dikembangkan untuk menyelesaikan masalah yang Anda hadapi. Kode Anda mungkin terlihat seperti ini

var async = require('async');

var calls = [];

['aaa','bbb','ccc'].forEach(function(name){
    calls.push(function(callback) {
        conn.collection(name).drop(function(err) {
            if (err)
                return callback(err);
            console.log('dropped');
            callback(null, name);
        });
    }
)});

async.parallel(calls, function(err, result) {
    /* this code will run after all calls finished the job or
       when any of the calls passes an error */
    if (err)
        return console.log(err);
    console.log(result);
});
aneh
sumber
Dengan ini ... metode forEach terjadi async. Jadi jika daftar objek lebih panjang dari 3 yang dirinci di sini, bukankah kasus ketika async.parallel (panggilan, fungsi (err, hasil) dievaluasi panggilan belum berisi semua fungsi dalam daftar asli?
Martin Beeby
5
@MartinBeeby forEachadalah sinkron. Lihat di sini: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Ada implementasi forEachdi bagian bawah. Tidak semuanya dengan callback asynchronous.
aneh
2
Sebagai catatan, async juga bisa digunakan di browser.
Erwin Wessels
@MartinBeeby Segala sesuatu dengan callback IS asynchronous, masalahnya adalah bahwa forEach tidak diteruskan "callback", tetapi hanya fungsi biasa (yang salah penggunaan terminologi oleh Mozilla). Dalam bahasa pemrograman fungsional, Anda tidak akan pernah memanggil fungsi yang diteruskan sebagai "callback"
3
@ ghert85 Tidak, tidak ada yang salah dengan terminologi. Callback hanyalah kode yang dapat dieksekusi yang diteruskan sebagai argumen ke kode lain dan diharapkan akan dieksekusi di beberapa titik. Itulah definisi standarnya. Dan itu bisa disebut sinkron atau asinkron. Lihat ini: en.wikipedia.org/wiki/Callback_(computer_programming)
aneh
128

Gunakan Janji .

var mongoose = require('mongoose');

mongoose.connect('your MongoDB connection string');
var conn = mongoose.connection;

var promises = ['aaa', 'bbb', 'ccc'].map(function(name) {
  return new Promise(function(resolve, reject) {
    var collection = conn.collection(name);
    collection.drop(function(err) {
      if (err) { return reject(err); }
      console.log('dropped ' + name);
      resolve();
    });
  });
});

Promise.all(promises)
.then(function() { console.log('all dropped)'); })
.catch(console.error);

Ini menjatuhkan setiap koleksi, mencetak "jatuh" setelah masing-masing, dan kemudian mencetak "semua jatuh" setelah selesai. Jika terjadi kesalahan, itu akan ditampilkan ke stderr.


Jawaban sebelumnya (ini mendahului dukungan asli Node untuk Promises):

Gunakan janji Q atau janji Bluebird .

Dengan Q :

var Q = require('q');
var mongoose = require('mongoose');

mongoose.connect('your MongoDB connection string');
var conn = mongoose.connection;

var promises = ['aaa','bbb','ccc'].map(function(name){
    var collection = conn.collection(name);
    return Q.ninvoke(collection, 'drop')
      .then(function() { console.log('dropped ' + name); });
});

Q.all(promises)
.then(function() { console.log('all dropped'); })
.fail(console.error);

Dengan Bluebird :

var Promise = require('bluebird');
var mongoose = Promise.promisifyAll(require('mongoose'));

mongoose.connect('your MongoDB connection string');
var conn = mongoose.connection;

var promises = ['aaa', 'bbb', 'ccc'].map(function(name) {
  return conn.collection(name).dropAsync().then(function() {
    console.log('dropped ' + name);
  });
});

Promise.all(promises)
.then(function() { console.log('all dropped'); })
.error(console.error);
Nate
sumber
1
Janji adalah jalan yang harus ditempuh. Bluebird adalah pustaka janji lain yang akan berfungsi dengan baik jika ini dalam kode kinerja-kritis. Ini harus menjadi pengganti drop-in. Gunakan saja require('bluebird').
weiyin
Saya telah menambahkan contoh Bluebird. Ini sedikit berbeda karena cara terbaik menggunakan Bluebird adalah menggunakan promisifyAllfitur tersebut.
Nate
Tahu bagaimana promisifyAll bekerja .. Saya sudah membaca dokumen tetapi saya tidak mengerti bagaimana cara menangani fungsi yang tidak sesuai dengan parameter function abc(data){, karena tidak seperti function abc(err, callback){...Pada dasarnya saya tidak berpikir semua fungsi mengambil kesalahan sebagai param pertama dan callback sebagai param kedua
Muhammad Umer
@MuhammadUmer Banyak detail di bluebirdjs.com/docs/api/promise.promisifyall.html
Nate
Sudah lama sejak driver MongoDB mendukung promise juga. Dapatkah Anda memperbarui contoh Anda untuk memanfaatkan ini? .map(function(name) { return conn.collection(name).drop() })
djanowski
21

Cara untuk melakukannya adalah dengan meneruskan tugas panggilan balik yang memperbarui penghitung bersama. Ketika penghitung bersama mencapai nol, Anda tahu bahwa semua tugas telah selesai sehingga Anda dapat melanjutkan aliran normal Anda.

var ntasks_left_to_go = 4;

var callback = function(){
    ntasks_left_to_go -= 1;
    if(ntasks_left_to_go <= 0){
         console.log('All tasks have completed. Do your stuff');
    }
}

task1(callback);
task2(callback);
task3(callback);
task4(callback);

Tentu saja, ada banyak cara untuk membuat kode semacam ini lebih umum atau dapat digunakan kembali dan salah satu dari banyak pustaka pemrograman asinkron di luar sana harus memiliki setidaknya satu fungsi untuk melakukan hal semacam ini.

hugomg
sumber
Ini mungkin bukan yang termudah untuk diterapkan, tetapi saya sangat suka melihat jawaban yang tidak memerlukan modul eksternal. Terima kasih!
mengimbangi
8

Memperluas jawaban @freakish, async juga menawarkan metode masing-masing, yang tampaknya sangat cocok untuk kasus Anda:

var async = require('async');

async.each(['aaa','bbb','ccc'], function(name, callback) {
    conn.collection(name).drop( callback );
}, function(err) {
    if( err ) { return console.log(err); }
    console.log('all dropped');
});

IMHO, ini membuat kode lebih efisien dan lebih terbaca. Saya telah mengambil kebebasan untuk menghapus console.log('dropped')- jika Anda menginginkannya, gunakan ini sebagai gantinya:

var async = require('async');

async.each(['aaa','bbb','ccc'], function(name, callback) {
    // if you really want the console.log( 'dropped' ),
    // replace the 'callback' here with an anonymous function
    conn.collection(name).drop( function(err) {
        if( err ) { return callback(err); }
        console.log('dropped');
        callback()
    });
}, function(err) {
    if( err ) { return console.log(err); }
    console.log('all dropped');
});
Erwin Wessels
sumber
5

Saya melakukan ini tanpa perpustakaan eksternal:

var yourArray = ['aaa','bbb','ccc'];
var counter = [];

yourArray.forEach(function(name){
    conn.collection(name).drop(function(err) {
        counter.push(true);
        console.log('dropped');
        if(counter.length === yourArray.length){
            console.log('all dropped');
        }
    });                
});
pengguna435943
sumber
4

Semua jawaban sudah cukup lama. Sejak awal 2013 Mongoose mulai mendukung promise secara bertahap untuk semua kueri, jadi itu akan menjadi cara yang direkomendasikan untuk menyusun beberapa panggilan asinkron dalam urutan yang diperlukan di masa mendatang.

Capaj
sumber
0

Dengan deferred(implementasi promise / deferred lain), Anda dapat melakukan:

// Setup 'pdrop', promise version of 'drop' method
var deferred = require('deferred');
mongoose.Collection.prototype.pdrop =
    deferred.promisify(mongoose.Collection.prototype.drop);

// Drop collections:
deferred.map(['aaa','bbb','ccc'], function(name){
    return conn.collection(name).pdrop()(function () {
      console.log("dropped");
    });
}).end(function () {
    console.log("all dropped");
}, null);
Mariusz Nowak
sumber
0

Jika Anda menggunakan Babel atau transpiler semacam itu dan menggunakan async / await, Anda dapat melakukan:

function onDrop() {
   console.log("dropped");
}

async function dropAll( collections ) {
   const drops = collections.map(col => conn.collection(col).drop(onDrop) );
   await drops;
   console.log("all dropped");
}
Ganaraj
sumber
Anda tidak dapat meneruskan panggilan balik ke drop()dan berharap untuk mengembalikan Janji. Bisakah Anda memperbaiki contoh ini dan menghapus onDrop?
djanowski