Frontender Magazine

Синхронная обработка задач в Gulp.JS

Gulp JS это менеджер задач ориентированный на фронтенд-разработку и ближайшая альтернатива Grunt JS.

Одно из нескольких отличий Gulp от Grunt заключается в том, что задачи (по умолчанию) запускаются асинхронно (вау, мне даже в словарь не пришлось заглядывать что бы написать это слово правильно с первого раза!). Приблизительно это означает, что все задачи запускаются одновременно.

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

В документации упоминается, как это делать, но после её прочтения несколько вопросов у меня все же оставалось, пока я с ними не разобрался.

Во первых, существуют 3 способа заставить задачу выполняться синхронно, но они должны использоваться вместе с зависимостями задач.

Первый из трех способов:

Передача колбека:

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

Возвращать поток:

gulp.task('sync', function () {  
    return gulp.src('js/*.js')
        .pipe(concat('script.min.js')
        .pipe(uglify())
        .pipe(gulp.dest('../dist/js');
});

Возвращать промис:

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

Допустим, у нас есть задача, которая требует выполнения созданной нами выше задачи sync (подойдет любая из трех).

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

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

В чем я ошибся, так это в том, что думал, что если я указываю несколько зависимостей они будут выполняться последовательно. Следующий код так НЕ РАБОТАЕТ:

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

// Я надеялся, что запустится задача sync,
// ЗАТЕМ задача thirdTask и ЗАТЕМ — default,
// но ЭТО НЕ ТАК. Этот код запустит ОДНОВРЕМЕННО
// sync И thirdTask и затем — default.
gulp.task('default', ['sync', 'thirdTask'], function () {  
    // делаем всякое
});

Что бы запускать default так, как я хотел, нужно было, кроме всего прочего, объявить sync зависимостью thirdTask:

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

gulp.task('default', ['sync', 'thirdTask'], function () {  
    // делаем всякое
});

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

В любом случае, надеюсь эта статья сможет помочь тем, кто пытается разобраться как запускать задачи в определенном порядке. И обратите внимание на то, что одна из вещей, которая делает Gulp быстрым и мощным это возможность запускать множество задач одновременно, так что запускайте их синхронно только тогда, когда это действительно нужно

Если вы заметили ошибку, вы всегда можете отредактировать статью, создать issue или просто написать об этом Антону Немцеву в skype ravencry.

Cameron Spear
Автор:
Cameron Spear
GitHub:
CWSpear
Twitter:
@CWSpear
Сaйт:
http://cameronspear.com/
Антон Немцев
Переводчик:
Антон Немцев
Сaйт:
http://frontender.info/
Twitter:
@silentimp
GitHub:
SilentImp
Skype:
ravencry

Комментарии (11 комментариев, если быть точным)

Автар пользователя
operatino

Берите https://github.com/OverZealous/run-sequence и не заморачивайтесь.

Автар пользователя
lexey111

Хорошая статья, спасибо. Хочу только посоветовать одну замечательную библиотеку - https://www.npmjs.org/package/run-sequence, которая предназначена именно для сложных последовательностей. У меня в проекте были случаи, когда задачи надо не только запускать параллельно/последовательно, но они при этом ещё могли и запускаться отдельно и с другими зависимостями, так что простое включение зависимости не помогало. А эта библиотека здорово выручила, хотя, конечно, есть много и других путей.

Вот пример вызова:

var runSequence = require('run-sequence'); return runSequence('clean', ['js', 'media', 'localize'], 'collect_garbage', callback);

выполнит clean, затем параллельно js, media, localize, а потом collect_garbage.

Автар пользователя
SilentImp

Или https://www.npmjs.org/package/gulp-sync Но вот только gulp умеет это все из коробки. Тогда зачем?

Автар пользователя
iamstarkov

Присоединюсь к вопросу от @SilentImp и также добавлю, что с релизом gulp@4.0.0 система менеджемента тасков обретёт новую жизнь и станет ещё круче

Автар пользователя
dmaslov

Еще можно добиться синхронности выполнения таким образом:

``` var afterTask = function(){ gulp.src(...) .pipe(...) .pipe(gulp.dest(...)); };

gulp.task('some-task', function() { gulp.src(..) .pipe(...) .pipe(gulp.dest(...)) .on('end', afterTask); }); ```

Автар пользователя
iamstarkov

фу-фу так делать, событие end не для этого. надо использовать run-sequence для красивой синхронной очереди тасков. Если хотите на ваниле всё сделать, то можно и по обычному

``` js // task мета-таск, так как будет ждать depTask, который, в свою очередь будет ждать anotherDepTask gulp.task('first', ['depTask']);

// deptask будет ждать anotherDepTask gulp.task('depTask', ['anotherDepTask'], function() {…});

// task без зависимостей gulp.task('depTask', ['anotherDepTask'], function() {…}); ```

Автар пользователя
t3kkitz

Больше нравится строить последовательности тасков через run-sequence:

js var runSequence = require('run-sequence'); gulp.task('build', function(callback) { runSequence('clean-build', 'images', 'css', 'html', ['copy-images','copy-fonts'], callback); });

Автар пользователя
r3b-fish

Поддерживаю первый комментарий. run-sequence решает задачи без головной боли.

p.s. кэпа: не юзайте gulp.run(), т.к. он уже deprecated.

Автар пользователя
Leikam

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

Автар пользователя
delch

Подскажите, а как орзанизовать правильный callback после выполнения таска, не создавая для этого нового таска ? К примеру у меня gulp-jade после обработки каждого файла выдает notification и по нескольку раз перезагружает livereload?!

Пример таска: gulp.task 'jade', -> gulp.src paths.views .pipe plumber errorHandler: notify.onError "Error: <%= error.message %>" .pipe jade pretty: true .pipe gulp.dest paths.app .pipe connect.reload() .pipe notify message: "jade task complete" open: "http://localhost:#{config.port}"

Автар пользователя
deni35

А нельзя просто дождаться конца потока? var stream = gulp.src(...)...; stream.waitForEnd();