Skip to content

Commit

Permalink
Merge pull request #150 from jtwebman/fix-nested-async-helpers
Browse files Browse the repository at this point in the history
Got nested async helpers working
  • Loading branch information
jtwebman authored Dec 2, 2018
2 parents 256a8af + 63b6296 commit c34d4cb
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 105 deletions.
90 changes: 0 additions & 90 deletions lib/async.js

This file was deleted.

16 changes: 16 additions & 0 deletions lib/generate-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict';

var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_';

function generateId(length) {
if (!length) {
length = 8;
}
var res = '';
for (var i = 0; i < length; ++i) {
res += alphabet[Math.floor(Math.random() * alphabet.length)];
}
return res;
}

module.exports = generateId;
51 changes: 40 additions & 11 deletions lib/hbs.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ var fs = require('fs');
var path = require('path');
var readdirp = require('readdirp');
var handlebars = require('handlebars');
var async = require('./async');
var resolver = require('./resolver');
var _ = require('lodash');

/**
* Regex pattern for layout directive. {{!< layout }}
Expand Down Expand Up @@ -357,15 +358,28 @@ ExpressHbs.prototype.compile = function(source, filename) {
*/
ExpressHbs.prototype.registerAsyncHelper = function(name, fn) {
this.handlebars.registerHelper(name, function(context, options) {
var resolverCache = this.resolverCache ||
_.get(context, 'data.root.resolverCache') ||
_.get(options, 'data.root.resolverCache');
if (!resolverCache) {
throw new Error('Could not find resolver cache in async helper ' + name + '.');
}
if (options && fn.length > 2) {
var resolver = function(arr, cb) {
var resolveFunc = function(arr, cb) {
return fn.call(this, arr[0], arr[1], cb);
};

return async.resolve(resolver.bind(this), [context, options]);
return resolver.resolve(
resolverCache,
resolveFunc.bind(this),
[context, options]
);
}

return async.resolve(fn.bind(this), context);
return resolver.resolve(
resolverCache,
fn.bind(this),
context
);
});
};

Expand Down Expand Up @@ -419,6 +433,7 @@ ExpressHbs.prototype.___express = function ___express(filename, source, options,
}

options.blockCache = {};
options.resolverCache = {};

this.viewsDir = options.settings.views || this.viewsDirOpt;
var self = this;
Expand Down Expand Up @@ -581,18 +596,32 @@ ExpressHbs.prototype.___express = function ___express(filename, source, options,
});
}

// Handles waiting for async helpers
function handleAsync(err, res) {
if (err) return cb(err);
async.done(function(values) {
function replaceValue(values, text) {
if (typeof text === 'string') {
Object.keys(values).forEach(function(id) {
res = res.replace(id, function() {
text = text.replace(id, function() {
return values[id];
});
res = res.replace(self.Utils.escapeExpression(id), function() {
text = text.replace(self.Utils.escapeExpression(id), function() {
return self.Utils.escapeExpression(values[id]);
});
});
}
return text;
}

// Handles waiting for async helpers
function handleAsync(err, res) {
if (err) return cb(err);
resolver.done(options.resolverCache, function(err, values) {
if (err) return cb(err);
Object.keys(values).forEach(function(key) {
values[key] = replaceValue(values, values[key]);
});
res = replaceValue(values, res);
if (resolver.hasResolvers(res)) {
return handleAsync(null, res);
}
cb(null, res);
});
}
Expand Down
45 changes: 45 additions & 0 deletions lib/resolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use strict';

var Promise = require('bluebird');

var generateId = require('./generate-id');

var ID_LENGTH = 8;
var MS_CHECK_TIME = 10;
var ID_PREFIX = '__aSyNcId_<_';
var ID_SUFFIX = '__';

function resolve(cache, fn, context) {
var id = ID_PREFIX + generateId(ID_LENGTH) + ID_SUFFIX;
cache[id] = new Promise(function(passed, failed) {
try {
fn(context, function(res) {
passed(res);
});
} catch(error) {
failed(error);
}
});
return id;
}

function done(cache, callback) {
Promise.props(cache).then(function(values) {
callback(null, values);
}).catch(function(error) {
callback(error);
});
}

function hasResolvers(text) {
if (text.search(ID_PREFIX) > 0) {
return true;
}
return false;
}

module.exports = {
done: done,
hasResolvers: hasResolvers,
resolve: resolve
};
3 changes: 1 addition & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@
"supertest": "2.0.1"
},
"dependencies": {
"bluebird": "^3.5.1",
"handlebars": "4.0.11",
"js-beautify": "1.7.5",
"lodash": "^4.17.10",
"readdirp": "2.1.0"
}
}
83 changes: 83 additions & 0 deletions test/apps/async/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,54 @@ var fs = require('fs');
var path = require('path');
var url = require('url');

var pages = [
{
id: 1,
title: 'Title 1'
},
{
id: 2,
title: 'Title 2'
},
{
id: 3,
title: 'Title 3'
}
]

var comments = [
{
id: 1,
page: 1,
subject: 'Title 1 Comment 1',
auther: 'JT'
},
{
id: 2,
page: 1,
subject: 'Title 1 Comment 2',
auther: 'Anna'
},
{
id: 3,
page: 1,
subject: 'Title 1 Comment 3',
auther: 'Jane'
},
{
id: 4,
page: 1,
subject: 'Title 1 Comment 4',
auther: 'Bob'
},
{
id: 5,
page: 4,
subject: 'This should not show!',
auther: 'Jill'
}
]

function getRandomNumber(min, max) {
return Math.random() * (max - min) + min;
}
Expand All @@ -31,12 +79,47 @@ function create(hbs, env) {
});
});

app.get('/fail', function (req, res) {
res.render('failer');
});

hbs.registerAsyncHelper('user', function(username, resultcb) {
setTimeout(function() {
resultcb(username);
}, getRandomNumber(100, 900))
});

hbs.registerAsyncHelper('pages', function(options, resultcb) {
var self = this;
setTimeout(function() {
var result = [];
for(var i = 0; i < pages.length; i++) {
options.data.page = pages[i];
result.push(options.fn.call(self, pages[i], options));
}
resultcb(result.join(''));
}, getRandomNumber(100, 900))
});

hbs.registerAsyncHelper('comments', function(options, resultcb) {
var self = this;
setTimeout(function() {
var result = [];
for(var i = 0; i < comments.length; i++) {
if (options.hash.page === comments[i].page) {
result.push(options.fn(comments[i]));
}
}
resultcb(result.join(''));
}, getRandomNumber(100, 300))
});

hbs.registerAsyncHelper('failer', function(_, resultcb) {
setTimeout(function() {
resultcb(options.fn());
}, 100);
})

return app;
}

Expand Down
4 changes: 4 additions & 0 deletions test/apps/async/views/failer.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{{#contentFor "title"}}
Failer
{{/contentFor}}
<span id="text">This should fail {{#failer}}for sure{{/failer}}.</span>
12 changes: 12 additions & 0 deletions test/apps/async/views/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,15 @@
{{message}} {{{user username}}}
{{/contentFor}}
<span id="text">This should welcome {{username}}.</span>
<div class="pages">
{{#pages filter="tags:test"}}
<div class="page">
<span class="title">{{title}}</title>
<div class="top-comments">
{{#comments top="3" page=id}}
<span class="subject">{{subject}}</span>-<span class="auther">{{auther}}</span>
{{/comments}}
</div>
</div>
{{/pages}}
</div>
Loading

0 comments on commit c34d4cb

Please sign in to comment.