简体   繁体   中英

Wait for a function to finish before firing the next in Node JS

I've got this gulp task:

// default task, runs through all primary tasks
gulp.task("default", ["media", "scripts", "styles", "html"], function () {
    // notify that task is complete
    gulp.src("gulpfile.js")
        .pipe(plugins.gulpif(ran_tasks.length, plugins.notify({title: "Success!", message: ran_tasks.length + " task" + (ran_tasks.length > 1 ? "s" : "") + " complete! [" + ran_tasks.join(", ") + "]", onLast: true})));

    // trigger FTP task if FTP flag is passed
    if (plugins.argv.ftp) {
        config_module.config(gulp, plugins, settings);
        ftp_module.upload(gulp, plugins, settings, ran_tasks, on_error);
    }

    // reset ran_tasks array
    ran_tasks.length = 0;
});

Which works great, except for the FTP bit. I need config_module.config() to finish before ftp_module.upload() can be triggered. I've tried setting up promises and anonymous functions with callbacks, but neither of those solutions worked; the FTP function keeps firing before config.

How can I make the ftp_module.upload() function wait for config_module.config() to finish before firing?


EDIT: Here's the promise I tried, which still doesn't work correctly:

new Promise(function (resolve) {
    config_module.config(gulp, plugins, settings);
    resolve();
}).then(function () {
    ftp_module.upload(gulp, plugins, settings, ran_tasks, on_error);
});

EDIT: I was hoping not to have to post the modules_config.config() code, as it's pretty long, but I think it's necessary to move on:

module.exports = {
    // config task, generate configuration file for FTP & BrowserSync and prompt dev for input
    config(gulp, plugins, settings) {
        // generate settings.json and start other functions
        const generate_config = function (callback) {
            return plugins.fs.stat("./settings.json", function (err) {
                if (err !== null) {
                    const json_data =
                    `{
                        "ftp": {
                            "dev": {
                                "hostname": "",
                                "port":     "21",
                                "mode":     "ftp",
                                "username": "",
                                "password": "",
                                "path":     ""
                            },
                            "dist": {
                                "hostname": "",
                                "port":     "21",
                                "mode":     "ftp",
                                "username": "",
                                "password": "",
                                "path":     ""
                            }
                        },
                        "browsersync": {
                            "dev": {
                                "proxy":  "",
                                "port":   "",
                                "open":   "",
                                "notify": ""
                            },
                            "dist": {
                                "proxy":  "",
                                "port":   "",
                                "open":   "",
                                "notify": ""
                            }
                        }
                    }`;

                    plugins.fs.writeFile("./settings.json", json_data, "utf8", function () {
                        callback();
                    });
                } else if (typeof callback === "function") {
                    callback();
                }
            });
        };

        // configue JSON data
        const configure_json = function (namespace, options, env, callback) {
            const prompts = [];

            // construct the prompts
            Object.keys(options).forEach(option => {
                const properties = options[option];

                // construct the prompt
                const prompt     = {
                    name:    option,
                    message: namespace + " " + option + ": ",
                };

                // construct the prompt
                Object.keys(properties).forEach(property => {
                    prompt[property] = properties[property];
                });

                // put the prompt in the array
                prompts.push(prompt);
            });

            // prompt the user
            return gulp.src("./settings.json")
                .pipe(plugins.prompt.prompt(prompts, function (res) {
                    // open settings.json
                    const file = plugins.json.read("./settings.json");

                    // update data in JSON
                    Object.keys(options).forEach(option => {
                        file.set(namespace + "." + env + "." + option, res[option]);
                        settings[namespace][option] = res[option];
                    });

                    // write updated file contents
                    file.writeSync();

                    if (typeof callback === "function") {
                        callback();
                    }
                }));
        };

        return new Promise (function (resolve) {
            // get the target environment
            const env = plugins.argv.dist ? "dist" : "dev";

            // generate settings.json
            generate_config(function () {
                // read browsersync settings from settings.json
                settings.browsersync.proxy  = plugins.json.read("./settings.json").get("browsersync." + env + ".proxy");
                settings.browsersync.port   = plugins.json.read("./settings.json").get("browsersync." + env + ".port");
                settings.browsersync.open   = plugins.json.read("./settings.json").get("browsersync." + env + ".open");
                settings.browsersync.notify = plugins.json.read("./settings.json").get("browsersync." + env + ".notify");

                // read FTP settingss from settings.json
                settings.ftp.host = plugins.json.read("./settings.json").get("ftp." + env + ".hostname");
                settings.ftp.port = plugins.json.read("./settings.json").get("ftp." + env + ".port");
                settings.ftp.mode = plugins.json.read("./settings.json").get("ftp." + env + ".mode");
                settings.ftp.user = plugins.json.read("./settings.json").get("ftp." + env + ".username");
                settings.ftp.pass = plugins.json.read("./settings.json").get("ftp." + env + ".password");
                settings.ftp.path = plugins.json.read("./settings.json").get("ftp." + env + ".path");

                // configure FTP credentials
                configure_json("ftp", {
                    hostname: {
                        default: settings.ftp.host,
                        type:    "input",
                    },
                    port: {
                        default: settings.ftp.port,
                        type:    "input",
                    },
                    mode: {
                        default: settings.ftp.mode === "ftp" ? 0 : settings.ftp.mode === "tls" ? 1 : settings.ftp.mode === "sftp" ? 2 : 0,
                        type:    "list",
                        choices: ["ftp", "tls", "sftp"],
                    },
                    username: {
                        default: settings.ftp.user,
                        type:    "input",
                    },
                    password: {
                        default: settings.ftp.pass,
                        type:    "password",
                    },
                    path: {
                        default: settings.ftp.path,
                        type:    "input",
                    },
                }, env, function () {
                    // configure BrowserSync settings
                    configure_json("browsersync", {
                        proxy: {
                            default: settings.browsersync.proxy === "" ? "localhost" : settings.browsersync.proxy,
                            type: "input",
                        },
                        port: {
                            default: settings.browsersync.port === "" ? "8080" : settings.browsersync.port,
                            type: "input",
                        },
                        open: {
                            default: settings.browsersync.open === "" ? "external" : settings.browsersync.open,
                            type: "input",
                        },
                        notify: {
                            default: settings.browsersync.open === "" ? "false" : settings.browsersync.open,
                            type: "input",
                        },
                    }, env, function () {
                        // resolve the promise
                        resolve();
                    });
                });
            });
        });
    }
};

As you can see, it is returning a promise, but for whatever reason I still can't get the FTP task to trigger after it.

You already have one possible answer to your question: promises.

The problem is that you are doing it wrongly.

In the code you posted in your edit (with promises), you are calling your config_module method (which seems to be asynchronous) and then you resolve the promise right after. In this scenario, because the method is asynchronous, the promise gets resolved before the processing on the config method is done, leading to a unwanted behavior.

The correct approach is that you should promisify the config_module method call itself. This way, you only "resolve" the promise after the execution of the method is fully done.

It is difficult to say how the config_module method should be since you didn't post its code. But you should create a promise in there and then resolve it only when the computation is done. So you could so something like:

config_module.config(gulp, plugins, settings)
    .then(function () { 
        ftp_module.upload(gulp, plugins, settings, ran_tasks, on_error);
    });

EDIT:

After you posted your config_module code, it was easier to see that the only thing missing was to use the promise returned by the config method to run the ftp_module.upload inside the .then block

 new Promise(function (resolve) { config_module.config(gulp, plugins, settings, ()=>{promise.resolve()}); }).then(function () { ftp_module.upload(gulp, plugins, settings, ran_tasks, on_error); }); config_module.config = (gulp, plugins, settings, doneCallback){ //...do stuff doneCallback() } 

One way to do it,

another way to do it would be that your function that must be chained return promises themselves, that way you can use a function like this one to chain them:

 var pipeline = (tasks, arg) => { return tasks.reduce((promise, fn) => { return promise.then(fn) }, Promise.resolve(arg)) } 

Basically when your chained functions resolve it's promise it calls the next one with the data you pass to resolve.

You must return the promise to make it sync .

But for that, your function must return a promise (like config_module.config and ftp_module.upload ) . If you don't have a promise returning function, you convert you callback function to promise using promisify .

new Promise(function () {
    var _config = Promise.promisify(config_module.config);  //make your callback a Promise
    return _config(gulp, plugins, settings);    //now call the function, and return its result (which is a Promise now)
}).then(function () {
    var _upload = Promise.promisify(ftp_module.upload);
    return _upload(gulp, plugins, settings, ran_tasks, on_error);
});

I suggest you use Async . It's a powerful module that helps to structure your application and makes control flow easier.

One of the functions Async provides is series which allows you to call functions one by one (ie the second function will not run until the first one has completed).

async.series(
   [
      function(callback) {
         // ...
         config_module.config();
         // do some more stuff if needed ...
         callback(null, 'one');
      },
      function(callback) {
         // ...
         ftp_module.upload();
         // do some more more stuff if needed ...
         callback(null, 'two');
      }
   ],
   // optional callback
   function(err, results) {
     // results is now equal to ['one', 'two']
   }
);

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