简体   繁体   中英

How to mix sync and async code in javascript in node.js and MongoDb

I have this code snippet:

var re = new RegExp("<a href=\"(news[^?|\"]+).*?>([^<]+)</a>", "g");
var match;
while (match = re.exec(body)){
    var href = match[1];
    var title = match[2];
    console.log(href);

    db.news.findOne({ title: title }, function(err, result){
        if (err) {
            console.log(err);
        } else {
            console.log(href);
            // more codes here
        }
    });
}

Here is the sample output:

news/2015/02/20/347332.html
news/2015/02/19/347307.html
news/2015/02/19/347176.html
news/2015/02/19/347176.html
news/2015/02/19/347176.html
news/2015/02/19/347176.html

So, I have three sets of data to be passed to findOne function. However, only the last one got passed in three times. How to workaround?

UPDATE based on jfriend00 and Neta Meta, these are the two ways to make it work:

var re = new RegExp("<a href=\"(news[^?|\"]+).*?>([^<]+)</a>", "g");
var cnt = 0;
function next(){
    var match = re.exec(body);
    if (match) {
        var href = match[1];
        var title = match[2];
        db.news.findOne({ title: title }, function(err, result){
            if (err) {
                console.log(err);
            } else {
                console.log(href);
                // more codes here
            }
        });
    }
}
next();

Or

var asyncFunction = function(db, href, title){
    db.news.findOne({ title: title }, function(err, result){
        if (err) {
            console.log(err);
        } else {
            console.log(href);
            // more codes here
        }
    });
}

var re = new RegExp("<a href=\"(news[^?|\"]+).*?>([^<]+)</a>", "g");
var match;
var cnt = 0;
while (match = re.exec(body)) {
    asyncFunction(db, match[1], match[2]);
}

The reason you don't get the output you expect is because you're sharing the href and title variables for all your database calls. Thus, those aren't kept track of separately for each async database operation.

If you're OK with all your async functions executing at once and the data can be processed in any order, then you just need to create a closure so capture your local variables separately for each invocation of the loop:

var re = new RegExp("<a href=\"(news[^?|\"]+).*?>([^<]+)</a>", "g");
var match, cntr = 0;
while (match = re.exec(body)){
    (function(href, title, index) {
        console.log(href);
        db.news.findOne({ title: title }, function(err, result){
            if (err) {
                console.log(err);
            } else {
                console.log(href);
                // more codes here
            }
        });
    })(match[1], match[2], cntr++);
}

If you want to issue the requests serially (only one at a time), then you can't really use the while loop to control things because it's going to launch them all at once. I tend to use this type of design pattern with a next() local function instead of the while loop for serial operation:

function someFunction() {

    var re = new RegExp("<a href=\"(news[^?|\"]+).*?>([^<]+)</a>", "g");

    function next() {
            var match = re.exec(body);
            if (match) {
                var href = match[1];
                var title = match[2];

                db.news.findOne({ title: title }, function(err, result){
                    if (err) {
                        console.log(err);
                    } else {
                        console.log(href);
                        // more codes here

                        // launch the next iteration
                        next();
                    }
                });
            }
    }

    // run the first iteration
    next();
}

Using promises, you could promisify() the db.news.findOne() function so it returns a promise, collect all the matches into an array and then use .reduce() to sequence all the database calls with the promise's .then() method providing the sequencing.

The reason you only get the last href is because while iterates and call fineOne which is an asyc operation. while wont wait till the findOne finishes it just continue running by the time the findOne finishes while got to the end of the loop and that's why you're getting the same href.

there are several ways you could do that, 1 promises(prefered in my opinion) - you will have to read about promisses to learn more. however checkout: https://github.com/petkaantonov/bluebird http://www.html5rocks.com/en/tutorials/es6/promises/ and http://promise-nuggets.github.io/articles/03-power-of-then-sync-processing.html

Wrapping your async function in another function and binding whatever you want to it ( not a good option but possible)

// wrapping your async function.
var asyncFunction = function(title,href, successCb, failCb){
    db.news.findOne({ title: title }, function(err, result){
        if (err) {
            failCb();
        } else {
            successCb()
        }
    });
};
var re = new RegExp("<a href=\"(news[^?|\"]+).*?>([^<]+)</a>", "g");
var match;
while (match = re.exec(body)){
    var href = match[1];
    var title = match[2];

    asyncFunction.call(this,title, href, function success(){}, function fail(){} );


}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM