-
Notifications
You must be signed in to change notification settings - Fork 15
/
index.js
146 lines (128 loc) · 5.39 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/*!
* promised-handlebars <https://github.com/nknapp/promised-handlebars>
*
* Copyright (c) 2015 Nils Knappmeier.
* Released under the MIT license.
*/
'use strict'
var deepAplus = require('deep-aplus')
var createMarkers = require('./lib/markers')
var utils = require('./lib/utils')
// Basic utility functions
var values = utils.values
var wrap = utils.wrap
var mapValues = utils.mapValues
var anyApplies = utils.anyApplies
var isPromiseAlike = utils.isPromiseAlike
module.exports = promisedHandlebars
/**
* Returns a new Handlebars instance that
* * allows helpers to return promises
* * creates `compiled` templates that always
* return a promise for the resulting string.
* The promise is fulfilled after all helper-promsises
* are resolved.
*
* @param {Handlebars} Handlebars the Handlebars engine to wrap
* @param {object=} options optional parameters
* @param {string=} options.placeholder the placeholder to be used in the template-output before inserting
* the promised results. This placeholder may not occur in the template or any partial. Neither
* my any helper generate the placeholder in to the result. Errors or wrong replacement will
* happen otherwise.
* @param {Promise=} options.Promise the promise implementation to use. Defaults to global.Promise
* @returns {Handlebars} a modified Handlebars object
*/
function promisedHandlebars (Handlebars, options) {
options = options || {}
options.placeholder = options.placeholder || '\u0001'
var Promise = options.Promise || global.Promise
if (!Promise) {
throw new Error('promised-handlebars: Promise is undefined. Please specify options.Promise or set global.Promise.')
}
var deep = deepAplus(Promise)
var Markers = createMarkers(Promise)
var engine = Handlebars.create()
var markers = null
// Wrap `registerHelper` with a custom function
engine.registerHelper = wrap(engine.registerHelper, function registerHelperWrapper (oldRegisterHelper, args) {
if (typeof args[0] === 'string' && typeof args[1] === 'function') {
var name = args[0]
var helper = args[1]
// Called like "registerHelper(name, helper)"
oldRegisterHelper.call(engine, name, wrap(helper, helperWrapper))
} else if (args.length === 1 && typeof args[0] === 'object') {
// Called like "registerHelper({ name: helper })
oldRegisterHelper.call(engine, mapValues(args[0], function (helper) {
return wrap(helper, helperWrapper)
}))
}
})
// Re-register all built-in-helpers to ensure that their methods are wrapped
engine.registerHelper(engine.helpers)
// Wrap the `compile` function, so that it wraps the compiled-template
// with `prepareAndResolveMarkers`
engine.compile = wrap(engine.compile, function compileWrapper (oldCompile, args) {
var fn = oldCompile.apply(engine, args)
// Wrap the compiled function
return wrap(fn, prepareAndResolveMarkers)
})
/**
* Wrapper for templates, partials and block-helper callbacks
*
* 1) the `markers` variable is initialized with a new instance of Markers
* 2) a promise is returned instead of a string
* 3) promise placeholder-values are replaced with the promise-results
* in the returned promise
*/
function prepareAndResolveMarkers (fn, args) {
if (markers) {
// The Markers-object has already been created for this cycle of the event loop:
// Just run the wraped function
return fn.apply(this, args)
}
try {
// No Markers yet. This is the initial call or some call that occured during a promise resolution
// Create markers, apply the function and resolve placeholders (i.e. promises) created during the
// function execution
markers = new Markers(engine, options.placeholder)
var resultWithPlaceholders = fn.apply(this, args)
return markers.resolve(resultWithPlaceholders)
} finally {
// Reset promises for the next execution run
markers = null
}
}
/**
* Wrapper for helper methods:
* * Call the helper after resolving parameters (if any promises are passed)
* * Wrap `options.fn` and `options.inverse` to return promises (if needed)
* * Convert helper results markers if they are promises.
*/
function helperWrapper (fn, args) {
var _this = this
var options = args[args.length - 1]
var hash = options.hash
// "fn" and "inverse" return strings. They must be able to handle promises
// just as the compiled template and partials
options.fn = options.fn && wrap(options.fn, prepareAndResolveMarkers)
options.inverse = options.inverse && wrap(options.inverse, prepareAndResolveMarkers)
// If there are any promises in the helper args or in the hash,
// the evaluation of the helper cannot start before these promises are resolved.
var promisesInArgs = anyApplies(args, isPromiseAlike)
var promisesInHash = anyApplies(values(hash), isPromiseAlike)
if (!promisesInArgs && !promisesInHash) {
// No promises in hash or args. Act a normal as possible.
var result = fn.apply(_this, args)
return isPromiseAlike(result) ? markers.asMarker(result) : result
}
var promise = deep(args).then(function (resolvedArgs) {
// We need "markers", because we are in a new event-loop-cycle now.
return prepareAndResolveMarkers(function () {
return fn.apply(_this, resolvedArgs)
})
})
return markers.asMarker(promise)
}
engine.Promise = Promise
return engine
}