วิธีที่ง่ายที่สุดในการรอให้งานอะซิงโครนัสบางอย่างเสร็จสมบูรณ์ใน Javascript?


112

ฉันต้องการทิ้งคอลเลคชัน mongodb แต่เป็นงานแบบอะซิงโครนัส รหัสจะเป็น:

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

คอนโซลจะแสดง:

all dropped
dropped
dropped
dropped

อะไรคือวิธีที่ง่ายที่สุดในการทำให้แน่ใจว่าall droppedจะพิมพ์ออกมาหลังจากที่คอลเลกชั่นทั้งหมดถูกทิ้ง? สามารถใช้บุคคลที่สามเพื่อลดความซับซ้อนของรหัสได้

คำตอบ:


92

ฉันเห็นคุณใช้อยู่mongooseดังนั้นคุณกำลังพูดถึง JavaScript ฝั่งเซิร์ฟเวอร์ ในคำแนะนำกรณีที่ฉันที่กำลังมองหาที่โมดูล asyncasync.parallel(...)และการใช้งาน คุณจะพบว่าโมดูลนี้มีประโยชน์มาก - ได้รับการพัฒนาเพื่อแก้ปัญหาที่คุณกำลังดิ้นรน รหัสของคุณอาจมีลักษณะเช่นนี้

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

ด้วยวิธีนี้ ... forEach จึงเกิดขึ้นแบบ async ดังนั้นหากรายการอ็อบเจ็กต์ยาวกว่า 3 รายละเอียดที่นี่อาจเป็นไปได้ไหมว่าเมื่อ async.parallel (การโทร, ฟังก์ชัน (err, ผลลัพธ์) ถูกประเมินการโทรยังไม่มีฟังก์ชันทั้งหมดในรายการดั้งเดิม
Martin Beeby

5
@MartinBeeby forEachเป็นซิงโครนัส ดูได้ที่นี่: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…มีการใช้งานforEachที่ด้านล่าง ไม่ใช่ทุกอย่างที่มีการโทรกลับไม่ตรงกัน
ประหลาด

2
สำหรับเร็กคอร์ด async ยังสามารถใช้ในเบราว์เซอร์ได้
Erwin Wessels

@MartinBeeby ทุกอย่างที่มีการเรียกกลับเป็นแบบอะซิงโครนัสปัญหาคือ forEach ไม่ได้ถูกส่งผ่าน "โทรกลับ" แต่เป็นเพียงฟังก์ชันปกติ (ซึ่ง Mozilla ใช้คำศัพท์ไม่ถูกต้อง) ในภาษาโปรแกรมที่ใช้งานได้คุณจะไม่เรียกฟังก์ชันที่ผ่านว่า "โทรกลับ"

3
@ ghert85 ไม่ได้มีอะไรผิดปกติกับคำศัพท์ การเรียกกลับเป็นเพียงโค้ดที่เรียกใช้งานได้ซึ่งถูกส่งผ่านเป็นอาร์กิวเมนต์ไปยังโค้ดอื่นและคาดว่าจะถูกเรียกใช้ในบางจุด นั่นคือคำจำกัดความมาตรฐาน และสามารถเรียกว่าซิงโครนัสหรืออะซิงโครนัส ดูสิ่งนี้: en.wikipedia.org/wiki/Callback_(computer_programming)
freakish

128

ใช้สัญญา

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

การทำเช่นนี้จะลดลงแต่ละคอลเลกชั่นการพิมพ์ "ตกหล่น" หลังจากแต่ละคอลเลกชั่นแล้วพิมพ์ "ทั้งหมดที่ตกหล่น" เมื่อเสร็จสมบูรณ์ stderrหากมีข้อผิดพลาดเกิดขึ้นก็จะแสดงให้


คำตอบก่อนหน้านี้ (ก่อนวันที่การสนับสนุน Node ของ Node สำหรับ Promises):

ใช้คำสัญญาQหรือสัญญาBluebird

ด้วย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);

ด้วย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
คำสัญญาคือหนทางที่จะไป Bluebirdเป็นอีกหนึ่งไลบรารีสัญญาที่จะทำงานได้ดีหากอยู่ในโค้ดที่มีความสำคัญต่อประสิทธิภาพ มันควรจะเป็นการทดแทนแบบดรอปอิน เพียงแค่ใช้require('bluebird').
weiyin

ฉันได้เพิ่มตัวอย่าง Bluebird แล้ว มันแตกต่างกันเล็กน้อยเนื่องจากวิธีที่ดีที่สุดในการใช้ Bluebird คือการใช้promisifyAllคุณสมบัตินี้
เนท

ความคิดใด ๆ ว่า promisifyAll ทำงานอย่างไร .. ฉันอ่านเอกสารแล้ว แต่ฉันไม่เข้าใจว่ามันจัดการกับฟังก์ชันที่ไม่มีพารามิเตอร์เช่นอย่างไร function abc(data){เพราะมันไม่เหมือนกับfunction abc(err, callback){...โดยทั่วไปฉันไม่คิดว่าฟังก์ชันทั้งหมดจะเกิดข้อผิดพลาดเป็นพารามิเตอร์แรกและการเรียกกลับเป็นพารามิเตอร์ที่ 2
Muhammad Umer

@MuhammadUmer รายละเอียดมากมายที่ bluebirdjs.com/docs/api/promise.promisifyall.html
เนท

เป็นเวลานานแล้วที่ไดรเวอร์ MongoDB รองรับสัญญาเช่นกัน คุณสามารถอัปเดตตัวอย่างเพื่อใช้ประโยชน์จากสิ่งนี้ได้หรือไม่? .map(function(name) { return conn.collection(name).drop() })
djanowski

21

วิธีการดำเนินการคือส่งผ่านงานการโทรกลับที่อัปเดตตัวนับที่ใช้ร่วมกัน เมื่อตัวนับที่ใช้ร่วมกันถึงศูนย์คุณจะรู้ว่างานทั้งหมดเสร็จสิ้นแล้วคุณจึงสามารถดำเนินการตามขั้นตอนปกติต่อไปได้

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

แน่นอนว่ามีหลายวิธีที่จะทำให้โค้ดประเภทนี้เป็นแบบทั่วไปหรือใช้ซ้ำได้และไลบรารีการเขียนโปรแกรม async จำนวนมากควรมีอย่างน้อยหนึ่งฟังก์ชันเพื่อทำสิ่งนี้


นี่อาจไม่ใช่วิธีที่ง่ายที่สุดในการนำไปใช้ แต่ฉันชอบเห็นคำตอบที่ไม่ต้องใช้โมดูลภายนอก ขอบคุณ!
counterbeing

8

เมื่อขยายคำตอบของ @freakish แล้ว async ยังเสนอวิธีการแต่ละอย่างซึ่งดูเหมือนจะเหมาะกับกรณีของคุณเป็นพิเศษ:

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 ทำให้โค้ดมีประสิทธิภาพมากขึ้นและอ่านง่ายขึ้น ฉันมีเสรีภาพในการลบconsole.log('dropped')- ถ้าคุณต้องการให้ใช้สิ่งนี้แทน:

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

ฉันทำสิ่งนี้โดยไม่มีไลบรารีภายนอก:

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

คำตอบทั้งหมดค่อนข้างเก่า ตั้งแต่ต้นปี 2013 พังพอนเริ่มให้การสนับสนุนคำสัญญาทีละน้อยสำหรับข้อความค้นหาทั้งหมดดังนั้นนี่จึงเป็นวิธีที่แนะนำในการจัดโครงสร้างการเรียกแบบ async หลาย ๆ ตัวตามลำดับที่ต้องการนับจากนี้ไป


0

ด้วยdeferred(คำสัญญาอื่น / การดำเนินการรอการตัดบัญชี) คุณสามารถทำได้:

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

หากคุณใช้ Babel หรือทรานส์ไพเลอร์ดังกล่าวและใช้ async / await คุณสามารถทำได้:

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

คุณไม่สามารถโทรกลับdrop()และคาดหวังว่าจะได้รับสัญญากลับมา คุณช่วยแก้ไขและลบตัวอย่างนี้ได้onDropไหม
djanowski
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.