Последовательное выполнение задач в Gulp JS

Original author: Cameron Spear
  • Translation
Gulp JS — это сборщик проектов и таск-менеджер для фронтенд и веб-разработки, который является достойной альтернативой для популярного Grunt JS. Одной из нескольких вещей, которыми Gulp отличается от Grunt является то, что по умолчанию все задачи запускаются асинхронно. В целом, можно сказать, что все задачи выполняются одновременно.

Недавно, изучая Gulp, я столкнулся с необходимостью синхронного запуска нескольких задач… синхронного. В документации к Gulp упоминается данная проблема, но мне пришлось немного повозиться перед тем, как я с этим разобрался.

Итак, у нас есть 3 способа сделать выполнение задач синхронным, но чтобы это заработало, нужно явно указать зависимости задач. Про зависимости чуть позже, для начала разберем эти 3 способа:

1. Используя обратный вызов, callback
gulp.task('sync', function (cb) {  
    // setTimeout может быть асинхронной задачей
    setTimeout(function () {
        cb();
    }, 1000);
});


2. Через возвращение потока
gulp.task('sync', function () {  
    return gulp.src('js/*.js')
        .pipe(concat('script.min.js')
        .pipe(uglify())
        .pipe(gulp.dest('../dist/js');
});


3. Воспользоваться Promise
gulp.task('sync', function () {  
    var deferred = Q.defer();
    // setTimeout может быть асинхронной задачей
    setTimeout(function () {
        deferred.resolve();
    }, 1000);
    return deferred.promise;
});


Теперь предположим, что мы имеем еще одну задачу secondTask, которая зависит от результатов выполнения задачи sync (которую мы создали одним из способов описанных выше). Поэтому мы объявляем нашу задачу sync как зависимость для задачи secondTask:

gulp.task('secondTask', ['sync'], function () {  
    // эта задача не буде запущена пока
    // задача 'sync' не закончит работу!
});


Главная ошибка, которую я допустил, была в предположении, что если я объявляю в качестве зависимости список задач, то задачи будут выполнены в указанной последовательности, когда выполнение следующей задачи в списке, будет выполняться только после окончания предшествующей задачи. Но это совсем не так:

gulp.task('thirdTask', function () {  
    // эта задача не зависит от других
});

// Я надеялся, что этот код сначала запустит
// задачу 'sync', затем будет запущена
// задача 'thridTask' и только потом будет
// выполнена задача 'default'. Но это не так.
// Перед выполнении задачи 'default', задачи
// 'sync' и 'thridTask будут запущены
// ОДНОВРЕМЕННО
gulp.task('default', ['sync', 'thirdTask'], function () {  
    // что-то делаем
});


Для того, чтобы задача default была выполнена в той последовательности как я хотел, задачу thirdTask необходимо сделать зависимой от задачи sync.

gulp.task('thirdTask', ['sync'] function () {  
    // теперь эта задача зависит от 'sync'. Если здесь
    // будет возвращен поток, то задача 'default' не будет
    // запущена, пока 'thirdTask' не будет закончена
});

gulp.task('default', ['sync', 'thirdTask'], function () {  
    // что-то делаем
});


Обратите внимание на то, что каждый раз, когда вы запускаете thirdTask, будет также запускаться sync. Это может быть нежелательным поведением, если у вас есть некоторая задача watch, которая запускает задачу thirdTask.

Надеюсь, что эта статья кому-то поможет понять, как с помощью Gulp реализовать выполнение своих задач в строго определенном порядке. Но стоит отметить, что именно асинхронный запуск и асинхронное выполнение задач делают Gulp таким быстрым и мощным, поэтому используйте возможность синхронного выполнения только там, где это действительно необходимо.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 11

    0
    Хм, а для gulp.js не какого-нибудь аналога grunt-concurrent?
      0
      А он и не нужен, gulp и так независимые задачи выполняет конкурентно.
        0
        Я очень давно мучаюсь со следующей проблемой:
        есть таск build, перед ним должен выполниться таск clean. А за ним все остальные сборщики запускаются. Это понятно, иначе он снесет папку с уже чем-то скомпиленным вполне вероятно.
        А зачастую нужно собрать один конкретный файл.
        Допустим логика такая:
        build -> [build-scripts, build-styles, build-html]
        build-scripts -> [clean], fn
        build-styles -> [clean], fn
        build-html -> [clean], fn

        А как сделать так, чтобы иметь возможность запускать и билд, и отдельные сборщики (без снесения всей папки) — без малейшего понятия.

        Вот это основная проблема галпа: он не предназначен для повторного использования тасков по большому счету.
        Хотя лучше него ничего нет все равно.
          0
          Возможно я не совсем понял проблему, но в чем сложность в тасках build-script/styles/html обновлять/сносить только то, что нужно, например, build-styles сносит только стили, а build-script только скрипты? Скажем, не использовать clean который сносит все, а разбить эту задачу на clean-style, clean-script, clean-html.
            0
            аэмм, ну это лишние таски, лишний гемморой и так далее. Да, так вроде бы абсолютно правильно, но просто больше пространства в сборщике съедает.
              0
              Не буду спорить, т.к. это дело вкуса и обстоятельств, просто для меня как-то проще и логичнее, когда каждый таск делает только свое дело, а уже потом из этих маленьких тасков строятся таски с расширенной функциональностью. Т.е. таск должен быть настолько простым, насколько это возможно, но не проще.
                0
                да я не спорю, сам задумался что так стоит делать :) но именно по простоте «снести папку» куда проще чем «снеси такие-такие-такие и такие файлы».
                А, вот, сообразил, в чем есть ньюанс.
                У меня периодически бывают подобные ситуации
                src/tooltipster.css
                src/style.sass

                компилируются в
                build/tooltipster.css
                build/style.css

                Все куда сложнее, но если вкратце — ситуация именно такая: есть неопределенный список расширений файлов на выходе, и если я собираю проект — я сношу их, в противном случае я хочу просто перезаписать. Объяснения дальше запутанные и сложные, но если вкратце — разносить по папкам типа assets/ не выйдет, постфиксы (типа style.sass.css) тоже не очень нравится как идея.
      +3
      Есть же run-sequence. Плюс скоро в новой версии orchestrator обещают примерно такой же функционал без необходимости подобных костылей.
      0
      а ещё можно просто сделать gulp.start('task_name') что бы не делать длинных цепочек callback-ов
      например для того что бы сделать релиз нам нужен момер версии, в таком случае:
        gulp.task('build', ['version'], function(){
         ...
           gulp.start('build_true')
         ...
      })
      

      где build_true — обычный сборщик модулей.

      Only users with full accounts can post comments. Log in, please.