【问题标题】:Merged gulp tasks never fire `end` event合并的 gulp 任务永远不会触发 `end` 事件
【发布时间】:2019-03-07 21:41:55
【问题描述】:

我有一个 gulp 任务,它遍历一个文件夹以查找子文件夹并根据每个文件夹的内容输出一个 JavaScript 文件。下面是一个更直观的示例。

    • 资产
      • 脚本
        • 严重
          • 加载CSS.init.js
        • 旧版
          • flexibility.init.js
          • picturefill.init.js
        • 现代
          • connectivity.js
          • layzr.init.js
          • menu_list.js
          • 滚动提示.init.js
          • slideout.init.js
          • swiper.init.js
        • 服务人员
          • service-worker.js

变成:

  • 开发
    • 资产
      • 脚本
        • critical.js
        • legacy.js
        • modern.js
        • service-worker.js

这是通过读取 src/assets/scripts 目录的内容,然后对每个文件夹(criticallegacymodernservice-worker)运行循环并将每个文件夹的内容发送到与merge-stream 合并在一起的 Gulp 任务。

这一切都很好,除了一旦任务重新合并在一起,我想在编译成功时触发通知。如果我尝试将任何内容通过管道传输到合并的流,它就不起作用。它只是返回合并的流,并且永远不会继续。

如果我不承诺我的 PROCESS_SCRIPTS 函数并且不使用合并流(即只处理一个手动指定的文件夹),它工作正常,所以我不知道发生了什么。

这是我的全部任务:

module.exports = {
    scripts(gulp, plugins, ran_tasks, on_error) {
        // task-specific plugins
        const ESLINT  = require("gulp-eslint");
        const WEBPACK = require("webpack-stream");

        // process scripts
        const PROCESS_SCRIPTS = (js_directory, destination_file_name = "modern.js", compare_file_name = "modern.js", source = [global.settings.paths.src + "/assets/scripts/*.js"]) => {
            return new Promise((resolve, reject) => {
                const WEBPACK_CONFIG = {
                    mode: "development",
                };

                // update webpack config for the current target destination and file name
                WEBPACK_CONFIG.mode   = plugins.argv.dist ? "production" : WEBPACK_CONFIG.mode;
                WEBPACK_CONFIG.output = {
                    filename: destination_file_name
                };

                const TASK = gulp.src(source)
                    // prevent breaking on error
                    .pipe(plugins.plumber({errorHandler: on_error}))
                    // check if source is newer than destination
                    .pipe(plugins.newer(js_directory + "/" + compare_file_name))
                    // lint all scripts
                    .pipe(ESLINT())
                    // print lint errors
                    .pipe(ESLINT.format())
                    // run webpack
                    .pipe(WEBPACK(WEBPACK_CONFIG))
                    // generate a hash and add it to the file name
                    .pipe(plugins.hash({template: "<%= name %>.<%= hash %><%= ext %>"}))
                    // output scripts to compiled directory
                    .pipe(gulp.dest(js_directory))
                    // generate a hash manfiest
                    .pipe(plugins.hash.manifest(".hashmanifest-scripts", {
                        deleteOld: true,
                        sourceDir: js_directory
                    }))
                    // output hash manifest in root
                    .pipe(gulp.dest("."))
                    // reject after errors
                    .on("error", () => {
                        reject(TASK);
                    })
                    // return the task after completion
                    .on("end", () => {
                        resolve(TASK);
                    });
            });
        };

        // scripts task, lints, concatenates, & compresses JS
        return new Promise ((resolve) => {
            // set JS directory
            const JS_DIRECTORY = plugins.argv.dist ? global.settings.paths.dist + "/assets/scripts" : global.settings.paths.dev + "/assets/scripts";

            // set the source directory
            const SOURCE_DIRECTORY = global.settings.paths.src + "/assets/scripts";

            // set up an empty merged stream
            const MERGED_STREAMS = plugins.merge();
            // get the script source folder list
            const SCRIPT_FOLDERS = plugins.fs.readdirSync(SOURCE_DIRECTORY);
            // get the script destination file list
            const SCRIPT_FILES   = plugins.fs.existsSync(JS_DIRECTORY) ? plugins.fs.readdirSync(JS_DIRECTORY) : false;

            // process all the script folders
            const PROCESS_SCRIPT_FOLDERS = () => {
                return Promise.resolve().then(() => {
                    // shift to the next folder
                    const FOLDER_NAME = SCRIPT_FOLDERS.shift();

                    // find the existing destination script file name
                    const FILE_NAME   = SCRIPT_FILES ? SCRIPT_FILES.find((name) => {
                        return name.match(new RegExp(FOLDER_NAME + ".[a-z0-9]{8}.js"));
                    }) : FOLDER_NAME + ".js";

                    // process all scripts, update the stream
                    return PROCESS_SCRIPTS(JS_DIRECTORY, FOLDER_NAME + ".js", FILE_NAME, SOURCE_DIRECTORY + "/" + FOLDER_NAME + "/**/*").then((processed) => {
                        MERGED_STREAMS.add(processed);
                    });
                }).then(() => SCRIPT_FOLDERS.length > 0 ? PROCESS_SCRIPT_FOLDERS() : resolve());
            };

            PROCESS_SCRIPT_FOLDERS().then(() => {
                // wrap up
                return MERGED_STREAMS
                    // prevent breaking on error
                    .pipe(plugins.plumber({
                        errorHandler: on_error,
                    }))
                    // notify that task is complete, if not part of default or watch
                    .pipe(plugins.gulpif(gulp.seq.indexOf("scripts") > gulp.seq.indexOf("default"), plugins.notify({
                        title:   "Success!",
                        message: "Scripts task complete!",
                        onLast:  true,
                    })))
                    // push task to ran_tasks array
                    .on("data", () => {
                        if (ran_tasks.indexOf("scripts") < 0) {
                            ran_tasks.push("scripts");
                        }
                    })
                    // resolve the promise on end
                    .on("end", () => {
                        resolve();
                    });
            });

        });
    }
};

在我的 GitHub 上也可以看到:https://github.com/JacobDB/new-site/blob/master/gulp-tasks/scripts.js


编辑:我已经尝试了一些东西,我会在这里详细说明。

  1. console.log("hello world") 不会在 MERGED_STREAMS.on("data")MERGED_STREAMS.on("error")MERGED_STREAMS.on("end") 之后触发。
  2. const MERGED_STREAMS = plugins.merge(); 移动到模块级变量(即紧跟在const WEBPACK = require("webpack-stream") 之后)不会改变结果。
  3. 执行#2 然后使用MERGED_STREAMS.add(gulp.src(source) ...) 而不是在promise 完成后添加流不会改变结果,除非离开.pipe(gulp.dist(".")) 时需要输出.hashmanifest,并且始终将任务标记为跑了。
  4. 禁用webpackhasheslint 的任何组合都不会产生影响。
  5. PROCESS_SCRIPTS 从返回承诺更改为返回流,然后将每个文件夹作为单独的变量处理,然后手动合并它们似乎可以正确触发运行的任务,但 webpack 只能运行一次,所以它只输出一个文件——critical.hash.js注意:我没有在禁用 hash 的情况下测试此方法,如果始终输出 .hashmanifest,这可能会导致它被标记为正确运行。
  6. 将 linting 步骤和 webpack 步骤拆分为单独的任务 kind of 会导致该任务被正确标记为已运行,但前提是 lint 任务不是承诺,这会导致unexpected end of stream 控制台出现错误。

编辑 2:根据@Louis 的建议更新了我的任务的修订版本。

【问题讨论】:

  • 没有什么特别不对劲的。最明显的检查是 MERGED_STREAMS 实际上有 .pipe().on() 方法。
  • 是的,这就是让我感到困惑的地方,看起来还不错。 typeof MERGED_STREAMS.ontypeof MERGED_STREAMS.pipe 都返回 function 所以我很确定这很好。
  • PROCESS_SCRIPT_FOLDERS().then(...) 回调实际上会触发?
  • 是的,如果我在它输出的那一行之后放一个console.log()
  • 是的,我会在有时间的时候继续研究它,如果我弄明白了会回来报告的。感谢您的帮助!

标签: javascript node.js promise stream gulp


【解决方案1】:

上面的代码有很多问题。使代码难以跟踪和调试的一个主要问题是您在不需要它的地方使用了new Promise一般来说,如果你有new Promise,并且promise executor内部的逻辑会根据另一个promise的结果来解决或拒绝,那么你就不需要使用new Promise

有时人们有这样的代码:

function foo() {
  const a = getValueSynchronously();
  const b = getOtherValueSynchronously();
  return doSomethingAsynchronous(a, b).then(x => x.someMethod());
}

假设doSomethigAsynchronous 返回一个承诺。上面的foo 函数的问题是,如果getValueSynchronouslygetOtherValueSynchronously 失败,那么foo 会引发异常,但是如果doSomethingAsynchronous 失败,那么它将拒绝一个promise。所以使用foo 的代码如果想要处理所有可能的失败,就必须处理同步异常异步拒绝。有时人们觉得他们可以通过将所有失败都视为承诺拒绝来解决问题:

function foo() {
  return new Promise((resolve, reject) => {
    const a = getValueSynchronously();
    const b = getOtherValueSynchronously();
    doSomethingAsynchronous(a, b).then(x => x.someMethod()).then(resolve, reject);
  });
}

在上面的代码中,如果getValueSynchronouslygetOtherValueSynchronously 失败,那就是一个promise 拒绝。但是上面代码的问题是很容易出错。您可以忘记在需要的任何地方致电reject。 (事实上​​,你确实在你的代码中有这个错误。你有嵌套的 promise,它们的拒绝不会被向上传播。它们只是丢失了,这意味着如果发生错误你的代码会在你不知道为什么的情况下停止。)或者你可能想在嵌套函数中调用 `resolve way down,这使得逻辑难以遵循。

你也可以这样做:

function foo() {
  return Promise.resolve().then(() => {
    const a = getValueSynchronously();
    const b = getOtherValueSynchronously();
    return doSomethingAsynchronous(a, b);
  }).then(x => x.someMethod());
}

您可以使用Promise.resolve() 进入应许世界(嗯……“应许之地?”)。在上面的代码中,您不必记得调用reject。如果对.then 的回调中的代码由于任何原因失败,你会得到一个被拒绝的promise。

我还注意到,在许多地方,您从传递给new Promise 的执行器函数返回了一个值。如果您没有在那里使用return,您的代码的行为将完全相同。为了说明,这段代码:

function foo() {
  return new Promise((resolve, reject) => {
    return doSomethingAsynchronous().then(resolve, reject);
  });
}

与此代码的行为完全相同

function foo() {
  return new Promise((resolve, reject) => {
    doSomethingAsynchronous().then(resolve, reject);
  });
}

执行器返回的值被忽略。故事结局。如果你认为你从你的 executor 那里返回的值正在做某事,那是不正确的。

【讨论】:

  • 谢谢,我想我明白你在说什么了。我对 JavaScript 还是比较陌生,完全是自学的,所以我很感激详细的解释!我怀疑我过度使用了returnnew Promise();我明天再看看这个并报告我的结果。
  • 好吧,我删除了一些 new Promise()return 的东西,仍然没有骰子,我确定我错过了一些明显的东西。用修改后的任务更新了我的问题。我怀疑问题出在.then(() =&gt; SCRIPT_FOLDERS.length &gt; 0 ? PROCESS_SCRIPT_FOLDERS() : resolve()); 中,但我不确定那里需要更改什么,好像我删除了resolve() 它永远不会完成任务。
猜你喜欢
  • 1970-01-01
  • 2018-02-20
  • 2011-11-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多