Asynchronous tasks in grunt.registerTask - gruntjs

I need to call two functions within grunt.registerTask, but the second function has to be called after the first function is done.
So I was wondering if we can use callbacks or promises or other asynchronous mechanisms within grunt.registerTask.
(More specifically, I need to launch karma in my first function call, and run karma in the second function call (to execute the initial unit tests). But in order to run karma, I need to launch it first. And that's what I'm missing.)

I had this:
grunt.registerTask("name_of_task", ["task_a", "task_b", "task_c"]);
And "task_b" had to be executed after "task_a" was done. The problem is that "task_a" is asynchronous and returns right away, so I needed a way to give "task_a" a few seconds to execute.
The solution:
grunt.registerTask("name_of_task", ["task_a", "task_b:proxy", "task_c"]);
grunt.registerTask("task_b:proxy", "task_b description", function () {
var done = this.async();
setTimeout(function () {
grunt.task.run("task_b");
done();
}, 2000);
});
};

From http://gruntjs.com/creating-tasks:
Tasks can be asynchronous.
grunt.registerTask('asyncfoo', 'My "asyncfoo" task.', function() {
// Force task into async mode and grab a handle to the "done" function.
var done = this.async();
// Run some sync stuff.
grunt.log.writeln('Processing task...');
// And some async stuff.
setTimeout(function() {
grunt.log.writeln('All done!');
done();
}, 1000);
});

For the sake of simplicity... having this Grunfile.js
grunt.registerTask('a', function () {
let done = this.async();
setTimeout(function () {
console.log("a");
done();
}, 3000);
});
grunt.registerTask('b', function () {
let done = this.async();
console.log("b1");
setTimeout(function () {
console.log("b2");
done();
}, 3000);
console.log("b3");
});
grunt.registerTask('c', function () {
console.log("c");
});
grunt.registerTask("run", ["a", "b", "c"]);
and then running run task, will produce the following output
Running "a" task
a
Running "b" task
b1
b3
b2 <-- take a look here
Running "c" task
c
Done.
The command is executed in this order
wait 3000 ms
console.log("a")
console.log("b1")
console.log("b3")
wait 3000 ms
console.log("b2")
console.log("c")

Related

Getting a possible leak using gulp watch, tasks take longer each time

I'm using gulp to compile and minify my SASS in an ASP.NET5 website to use as a CDN. I've written a watcher that watches my *.scss files, it then uses dependencies to compile the scss and then concat/minify it into one style.min.css.
The issue I'm having is that the longer the project is open, or at least with each sequential run of the gulp tasks, they get considerably longer to the point where I had only been developing for about 20 minutes, and the compile task was taking 15+ second, and the minify was taking 25+ seconds. The very first run of each takes about 1 or 2 milliseconds so I'm really lost as to what's going on.
Here's my watcher:
gulp.task('Watch:Sass', function () {
watch(paths.cdn.sassLoc, { verbose: true }, function () {
gulp.start("Min:css");
});
});
Here is the Min:css task:
gulp.task("Min:css", ["Compile:Sass"], function () {
return gulp.src([paths.cdn.cssLoc, "!" + paths.cdnMinCssLoc + "**/*.*"])
.pipe(concat(paths.cdn.cssDest))
.pipe(cssmin())
.pipe(plumber({
handleError: function (err) {
console.log(err);
this.emit('end');
}
}))
.pipe(gulp.dest("."));
});
And here is the Compile:Sass task that is being injected as a dependency:
gulp.task('Compile:Sass', function () {
gulp.src(paths.cdn.imagesLoc)
.pipe(gulp.dest(paths.webroot + '/css/min/images'));
return gulp.src(paths.cdn.sassLoc).pipe(plumber({
handleError: function (err) {
console.log(err);
this.emit('end');
}
}))
.pipe(sass().on('error', sass.logError))
.pipe(gulp.dest(paths.cdn.sassDest));
});
Could it be because of my use of returns?
Any help would be greatly appreciated as I have to restart Visual Studio every half hour or so which gets extremely frustrating!
I'm sure I've included everything relevant, but please ask if you require any more info.
In this case it was in fact my return statements that were in the wrong place.
In order to fix the issue, I had to take off the return on the gulp.src(...) and I had to add a return true to the end of the Compile:Sass gulp task. I also took the return off of the Min:Css task completely and now my compile task takes ~1.5ms and my min task takes ~600us.
Here's the final Compile:Sass task:
gulp.task('Compile:Sass', function () {
gulp.src(paths.cdn.imagesLoc)
.pipe(gulp.dest(paths.webroot + '/css/min/images'));
gulp.src(paths.cdn.sassLoc).pipe(plumber({
handleError: function (err) {
console.log(err);
this.emit('end');
}
}))
.pipe(sass().on('error', sass.logError))
.pipe(gulp.dest(paths.cdn.sassDest));
return true;
});
Matt, you have a concat on one of your "Min:css" task pipes.
gulp.task("Min:css", ["Compile:Sass"], function () {
return gulp.src([paths.cdn.cssLoc, "!" + paths.cdnMinCssLoc + "**/*.*"])
.pipe(concat(paths.cdn.cssDest))
...
...
.pipe(gulp.dest("."));
});
Make sure that you are not infinitely concatenating that same css over and over and over again. I can't tell what your paths.cdn.cssLoc points to. That would account for longer and longer processing time. You can call that a leak if you want.

How to use Meteor.wrapAsync on the client?

In my Meteor client code, I'm trying to use a third party API that only has asynchronous calls. How can I use Meteor.wrapAsync on the client to make calls to this API in a synchronous style? The docs seem to indicate that this is possible: http://docs.meteor.com/#/full/meteor_wrapasync
Here is some sample code I would like to call in a synchronous style:
var func1 = function(callback) {
Meteor.setTimeout(function() {
console.log('func1 executing');
callback(null, {success: true});
}, 2000);
};
var func2 = function(callback) {
Meteor.setTimeout(function() {
console.log('func2 executing');
callback(null, {success: true});
}, 1000);
};
var wrapped1 = Meteor.wrapAsync(func1);
var wrapped2 = Meteor.wrapAsync(func2);
Template.test.rendered = function() {
wrapped1();
console.log('After wrapped1()');
wrapped2();
console.log('After wrapped2()');
};
Currently, this produces this output:
After wrapped1()
After wrapped2()
func2 executing
func1 executing
I'd like it to produce:
func1 executing
After wrapped1()
func2 executing
After wrapped2()
I've put this code into a MeteorPad here: http://meteorpad.com/pad/fLn9DXHf7XAACd9gq/Leaderboard
Meteor.wrapAsync works on the client for the sake of isomorphic code. This is so you can make code that you can share on the client and server without Meteor crashing or complaining.
It is not possible to have synchronous code on the client. At least without using ES6 features which are not available on all browsers.
As in the comment by saeimeunt Meteor.wrapAsync will require a callback on the client.

Test asynchronous functionality in Jasmine 2.0.0 with done()

I am trying to implement a jasmine test on a simple promise implementation (asynchronous code) with the done() function and my test fails although the code being tested works perfectly fine.
Can anyone please help me to figure out what is missing in my test?
var Test = (function () {
function Test(fn) {
this.tool = null;
fn(this.resolve.bind(this));
}
Test.prototype.then = function (cb) {
this.callback = cb;
};
Test.prototype.resolve = function (value) {
var me = this;
setTimeout(function () {
me.callback(value);
}, 5000);
};
return Test;
})();
describe("setTimeout", function () {
var test, newValue = false,
originalTimeout;
beforeEach(function (done) {
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
test = new Test(function (cb) {
setTimeout(function () {
cb();
}, 5000);
});
test.then(function () {
newValue = true;
console.log(1, newValue);
done();
});
});
it("Should be true", function (done) {
expect(1).toBe(1);
expect(newValue).toBeTruthy();
});
afterEach(function () {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});
});
the same test in jsfiddle: http://jsfiddle.net/ravitb/zsachqpg/
This code is testing a simple promise like object, so will call the Test object a promise for convenience.
There are two different async events after the promise creation:
1. The call to the .then() method
2. The resolving of the promise by calling the cb() function in the beforeEach() function.
In the real world these two can be called in any order and at any time.
For the test, the .then() call must be moved to the it() section's callback and all spec methods (e.g expect()) need to be called in it's callback or they'll run before it's resolved. The beforeEach() is part of the test setup while the it() function is the spec, the test itself.
The done() method needs to be called twice,
When the beforeEach() async action is finished (i.e after the cb() is called), that will start running the spec. So it should look something like this:
beforeEach(function (done) {
test = new Test(function (cb) {
setTimeout(function () {
console.log("in beforeEach() | setTimeout()");
cb(resolvedValue);
done()
}, 500);
});
});
When the spec's (it() section's) async action is finished inside the .then() method after all calls to jasmine test methods, this will tell Jasmine the spec finished running (and so the time-out won't be reached). So:
it("Should be " + resolvedValue, function (done) {
test.then(function (value) {
console.log("in then()");
expect(value).toBe(resolvedValue);
done();
});
});
Also, as you can see instead of testing that a variable's value has changed I'm testing that the value passed to the .then() method is the same as the one passed to the promise resolve cb() function as that is the right behaviour you are expecting.
Here's an updated version of your fiddle.
You can check in the browser's console to see that all callbacks are being called
Note: Changing Jasmine's DEFAULT_TIMEOUT_INTERVAL just makes it more convoluted for no reason, so I removed it and some extraneous code.

Run a command after a grunt task finishes?

I want to run a command but after a task finishes in grunt.
uglify: {
compile: {
options: {...},
files: {...}
}
?onFinish?: {
cmd: 'echo done!',
// or even just a console.log
run: function(){
console.log('done!');
}
}
},
Either run a command in shell, or even just be able to console.log. Is this possible?
Grunt does not support before and after callbacks, but next version could implement events that would work in the same way, as discussed in issue #542.
For now, you should go the task composition way, this is, create tasks for those before and after actions, and group them with a new name:
grunt.registerTask('newuglify', ['before:uglify', 'uglify', 'after:uglify']);
Then remember to run newuglify instead of uglify.
Another option is not to group them but remember to add the before and after tasks individually to a queue containing uglify:
grunt.registerTask('default', ['randomtask1', 'before:uglify', 'uglify', 'after:uglify', 'randomtask2']);
For running commands you can use plugins like grunt-exec or grunt-shell.
If you only want to print something, try grunt.log.
The grunt has one of the horrible code that I've ever seen. I don't know why it is popular. I would never use it even as a joke. This is not related to "legacy code" problem. It is defected by design from the beginning.
var old_runTaskFn = grunt.task.runTaskFn;
grunt.task.runTaskFn = function(context, fn, done, asyncDone) {
var callback;
var promise = new Promise(function(resolve, reject) {
callback = function (err, success) {
if (success) {
resolve();
} else {
reject(err);
}
return done.apply(this, arguments);
};
});
something.trigger("new task", context.name, context.nameArgs, promise);
return old_runTaskFn.call(this, context, fn, callback, asyncDone);
}
You can use callback + function instead of promise + trigger. This function will request the new callback wrapper for new task.

passing grunt parameters from one task to another

I'm trying to pass the configuration values returned from the server(zookeeper) into compass (cdnHost, environment, etc) and seem to be having a hard time using the right approach.
I looked at ways to pass around args from one task to another on this page as a starting point
http://gruntjs.com/frequently-asked-questions#how-can-i-share-parameters-across-multiple-tasks
module.exports = function(grunt) {
grunt.initConfig({
compass: {
dist: {
//options: grunt.option('foo')
//options: global.bar
options: grunt.config.get('baz')
}
},
...
grunt.registerTask('compassWithConfig', 'Run compass with external async config loaded first', function () {
var done = this.async();
someZookeeperConfig( function () {
// some global.CONFIG object from zookeeper
var config = CONFIG;
// try grunt.option
grunt.option('foo', config);
// try config setting
grunt.config.set('bar', config);
// try global
global['baz'] = config;
done(true);
});
});
...
grunt.registerTask('default', ['clean', 'compassWithConfig', 'compass']);
I also tried calling the compass task directly, and it made no difference.
grunt.task.run('compass');
Any insights would be greatly appreciated. (e.g. way to use initConfig and have the value be available).
Thanks
When you write:
grunt.initConfig({
compass: {
dist: {
options: grunt.config.get('baz')
}
}
... grunt.config is called right away, and returns the value of baz as it is right now. Altering it (later) in another task simply won't get picked-up.
How to solve that?
#1: update compass.dist.options instead of updating baz
grunt.registerTask('compassWithConfig', 'Run compass with external async config loaded first', function () {
var done = this.async();
someZookeeperConfig( function () {
// some global.CONFIG object from zookeeper
var config = CONFIG;
grunt.config.set('compass.dist.options', config);
done();
});
});
Now, running task compassWithConfig first, then task compass will get the result you expect.
#2: wrap-up compass task execution in order to abstract away config mapping
grunt.registerTask('wrappedCompass', '', function () {
grunt.config.set('compass.dist.options', grunt.config.get('baz'));
grunt.task.run('compass');
});
// Then, you can manipulate 'baz' without knowing how it needs to be mapped for compass
grunt.registerTask('globalConfigurator', '', function () {
var done = this.async();
someZookeeperConfig( function () {
// some global.CONFIG object from zookeeper
var config = CONFIG;
grunt.config.set('baz', config);
done();
});
});
Finally, running task globalConfigurator then wrappedCompass will get you to the result.

Resources