Cách đơn giản nhất để đợi một số tác vụ không đồng bộ hoàn tất, trong Javascript?


112

Tôi muốn bỏ một số bộ sưu tập mongodb, nhưng đó là một tác vụ không đồng bộ. Mã sẽ là:

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

Bảng điều khiển hiển thị:

all dropped
dropped
dropped
dropped

Cách đơn giản nhất để đảm bảo all droppedsẽ được in sau khi tất cả các bộ sưu tập đã bị loại bỏ là gì? Bất kỳ bên thứ 3 nào cũng có thể được sử dụng để đơn giản hóa mã.

Câu trả lời:


92

Tôi thấy bạn đang sử dụng mongoosevì vậy bạn đang nói về JavaScript phía máy chủ. Trong trường hợp đó, tôi khuyên bạn nên xem xét mô-đun không đồng bộ và sử dụng async.parallel(...). Bạn sẽ thấy mô-đun này thực sự hữu ích - nó được phát triển để giải quyết vấn đề bạn đang gặp phải. Mã của bạn có thể trông như thế này

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

Với điều này ... phương thức forEach xảy ra không đồng bộ. Vì vậy, nếu danh sách đối tượng dài hơn 3 chi tiết ở đây, có thể không xảy ra trường hợp khi async.parallel (cuộc gọi, hàm (err, kết quả) được đánh giá các cuộc gọi chưa chứa tất cả các hàm trong danh sách ban đầu?
Martin Beeby

5
@MartinBeeby forEachlà đồng bộ. Hãy xem tại đây: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Có phần triển khai forEachở phía dưới. Không phải mọi thứ với callback đều không đồng bộ.
quái đản

2
Đối với bản ghi, async cũng có thể được sử dụng trong trình duyệt.
Erwin Wessels

@MartinBeeby Mọi thứ có IS gọi lại đều không đồng bộ, vấn đề là forEach không được truyền "callback", mà chỉ là một hàm thông thường (Mozilla sử dụng sai thuật ngữ). Trong một ngôn ngữ lập trình chức năng, bạn sẽ không bao giờ gọi một chức năng thông qua một "gọi lại"

3
@ ghert85 Không, không có gì sai với thuật ngữ. Gọi lại đơn giản là bất kỳ mã thực thi nào được chuyển làm đối số cho mã khác và dự kiến ​​sẽ được thực thi tại một số điểm. Đó là định nghĩa tiêu chuẩn. Và nó có thể được gọi là đồng bộ hoặc không đồng bộ. Xem cái này: en.wikipedia.org/wiki/Callback_(computer_programming)
kỳ lạ

128

Sử dụng Lời hứa .

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

Thao tác này sẽ giảm từng bộ sưu tập, in “rơi” sau mỗi bộ, và sau đó sẽ in “tất cả bị rơi” khi hoàn tất. Nếu một lỗi xảy ra, nó được hiển thị cho stderr.


Câu trả lời trước (hỗ trợ gốc của Node cho Promises trước ngày này):

Sử dụng lời hứa Q hoặc lời hứa Bluebird .

Với 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);

Với 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);

1
Lời hứa là con đường để đi. Bluebird là một thư viện hứa hẹn khác sẽ hoạt động tốt nếu đây là mã quan trọng về hiệu suất. Nó phải là một thay thế thả trong. Chỉ cần sử dụng require('bluebird').
weiyin

Tôi đã thêm một ví dụ về Bluebird. Có một chút khác biệt vì cách tốt nhất để sử dụng Bluebird là sử dụng promisifyAlltính năng này.
Nate

Bất kỳ ý tưởng nào về cách promisifyAll hoạt động..Tôi đã đọc tài liệu nhưng tôi không hiểu là cách nó xử lý các chức năng không phải tham số như thế nào function abc(data){, bởi vì nó không giống như function abc(err, callback){...Về cơ bản, tôi không nghĩ rằng tất cả các hàm đều nhận lỗi như tham số đầu tiên và gọi lại như tham số thứ hai
Muhammad Umer

@MuhammadUmer Rất nhiều thông tin chi tiết tại bluebirdjs.com/docs/api/promise.promisifyall.html
Nate

Đã lâu rồi kể từ khi trình điều khiển MongoDB cũng hỗ trợ các hứa hẹn. Bạn có thể cập nhật ví dụ của mình để tận dụng lợi thế này không? .map(function(name) { return conn.collection(name).drop() })
djanowski

21

Cách thực hiện là chuyển các nhiệm vụ một cuộc gọi lại cập nhật bộ đếm dùng chung. Khi bộ đếm chia sẻ đạt đến 0, bạn biết rằng tất cả các tác vụ đã hoàn thành để bạn có thể tiếp tục với quy trình bình thường của mình.

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

Tất nhiên, có nhiều cách để làm cho loại mã này trở nên chung chung hơn hoặc có thể tái sử dụng và bất kỳ thư viện lập trình không đồng bộ nào hiện có ít nhất một chức năng để thực hiện loại mã này.


Đây có thể không phải là cách dễ thực hiện nhất, nhưng tôi thực sự thích thấy một câu trả lời không yêu cầu các mô-đun bên ngoài. Cảm ơn bạn!
counterbeing

8

Mở rộng dựa trên câu trả lời @freakish, async cũng cung cấp từng phương pháp, có vẻ đặc biệt phù hợp với trường hợp của bạn:

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, điều này làm cho mã hiệu quả hơn và dễ đọc hơn. Tôi đã tự do xóa console.log('dropped')- nếu bạn muốn, hãy sử dụng nó để thay thế:

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

5

Tôi làm điều này mà không có luật sư bên ngoài:

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

4

Tất cả các câu trả lời đều khá cũ. Kể từ đầu năm 2013, Mongoose bắt đầu hỗ trợ dần dần các hứa hẹn cho tất cả các truy vấn, vì vậy tôi đoán đó sẽ là cách được đề xuất để cấu trúc một số lệnh gọi không đồng bộ theo thứ tự bắt buộc về sau.


0

Với deferred(một lời hứa khác / triển khai hoãn lại) bạn có thể làm:

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

0

Nếu bạn đang sử dụng Babel hoặc các bộ chuyển đổi tương tự và sử dụng async / await, bạn có thể làm:

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

Bạn không thể chuyển cuộc gọi lại tới drop()và mong đợi trả lại Lời hứa. Bạn có thể vui lòng sửa chữa ví dụ này và loại bỏ onDrop?
djanowski 20/09/2016
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.