Skip to content

Commit 751a8ba

Browse files
JorgenEvensdave-irvine
authored andcommitted
Asynchronous rendering and function/filter promises (twigjs#457)
* [parse] Allow asynchronous usage of Twig.expression.parse. * [parse] Add testcase for async functions. * [parse] Extend async behaviour to filters. * [parse] Add asynchronous error handling. * [async.forEach] Cleanup implementation. No longer uses callbacks, but rather uses the Twig.Promise implementation. * [logic] Handle asynchronous functions and filters in logic. * [expressions] Fixup function handling after rebase. * [docs] Add documentation on asynchronous implementation. * [async] Add more test cases
1 parent d78ee6b commit 751a8ba

File tree

8 files changed

+1303
-476
lines changed

8 files changed

+1303
-476
lines changed

ASYNC.md

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
# Twig Asynchronous Rendering
2+
3+
## Synchronous promises
4+
5+
The asynchronous behaviour of Twig.js relies on promises, in order to support both the synchronous and asynchronous behaviour there is an internal promise implementation that runs fully synchronous.
6+
7+
The internal implementation of promises does not use `setTimeout` to run through the promise chain, but instead synchronously runs through the promise chain.
8+
9+
The different promise implementations can be mixed, synchronous behaviour however is no longer guaranteed as soon as the regular promise implementation is run.
10+
11+
### Examples
12+
13+
**Internal (synchronous) implementation**
14+
15+
[Internal implementation](https://github.com/JorgenEvens/twig.js/tree/master/src/twig.async.js#L40)
16+
17+
```javascript
18+
console.log('start');
19+
Twig.Promise.resolve('1')
20+
.then(function(v) {
21+
console.log(v);
22+
return '2';
23+
})
24+
.then(function(v) {
25+
console.log(v);
26+
});
27+
console.log('stop');
28+
29+
/**
30+
* Prints to the console:
31+
* start
32+
* 1
33+
* 2
34+
* stop
35+
*/
36+
```
37+
38+
**Regular / native promises**
39+
40+
Implementations such as the native promises or [bluebird](http://bluebirdjs.com/docs/getting-started.html) promises.
41+
42+
```javascript
43+
console.log('start');
44+
Promise.resolve('1')
45+
.then(function(v) {
46+
console.log(v);
47+
return '2';
48+
})
49+
.then(function(v) {
50+
console.log(v);
51+
});
52+
console.log('stop');
53+
54+
/**
55+
* Prints to the console:
56+
* start
57+
* stop
58+
* 1
59+
* 2
60+
*/
61+
```
62+
63+
**Mixing promises**
64+
65+
```javascript
66+
console.log('start');
67+
Twig.Promise.resolve('1')
68+
.then(function(v) {
69+
console.log(v);
70+
return Promise.resolve('2');
71+
})
72+
.then(function(v) {
73+
console.log(v);
74+
});
75+
console.log('stop');
76+
77+
/**
78+
* Prints to the console:
79+
* start
80+
* 1
81+
* stop
82+
* 2
83+
*/
84+
```
85+
86+
87+
## Async helpers
88+
89+
To preserve the correct order of execution there is an implemenation of `Twig.forEach()` that waits any promises returned from the callback before executing the next iteration of the loop. If no promise is returned the next iteration is invoked immediately.
90+
91+
```javascript
92+
var arr = new Array(5);
93+
94+
Twig.async.forEach(arr, function(value, index) {
95+
console.log(index);
96+
97+
if (index % 2 == 0)
98+
return index;
99+
100+
return Promise.resolve(index);
101+
})
102+
.then(function() {
103+
console.log('finished');
104+
});
105+
106+
/**
107+
* Prints to the console:
108+
* 0
109+
* 1
110+
* 2
111+
* 3
112+
* 4
113+
* finished
114+
*/
115+
```
116+
117+
## Switching render mode
118+
119+
The rendering mode of Twig.js internally is determined by the `allow_async` argument that can be passed into `Twig.expression.parse`, `Twig.logic.parse`, `Twig.parse` and `Twig.Template.render`. Detecting if at any point code runs asynchronously is explained in [detecting asynchronous behaviour](#detecting-asynchronous-behaviour).
120+
121+
For the end user switching between synchronous and asynchronous is as simple as using a different method on the template instance.
122+
123+
**Render template synchronously**
124+
125+
```javascript
126+
var output = twig({
127+
data: 'a {{value}}'
128+
}).render({
129+
value: 'test'
130+
});
131+
132+
/**
133+
* Prints to the console:
134+
* a test
135+
*/
136+
```
137+
138+
**Render template asynchronously**
139+
140+
```javascript
141+
var template = twig({
142+
data: 'a {{value}}'
143+
}).renderAsync({
144+
value: 'test'
145+
})
146+
.then(function(output) {
147+
console.log(output);
148+
});
149+
150+
/**
151+
* Prints to the console:
152+
* a test
153+
*/
154+
```
155+
156+
## Detecting asynchronous behaviour
157+
158+
The pattern used to detect asynchronous behaviour is the same everywhere it is used and follows a simple pattern.
159+
160+
1. Set a variable `is_async = true`
161+
2. Run the promise chain that might contain some asynchronous behaviour.
162+
3. As the last method in the promise chain set `is_async = false`
163+
4. Underneath the promise chain test whether `is_async` is `true`
164+
165+
This pattern works because the last method in the chain will be executed in the next run of the eventloop (`setTimeout`/`setImmediate`).
166+
167+
### Examples
168+
169+
**Synchronous promises only**
170+
171+
```javascript
172+
var is_async = true;
173+
174+
Twig.Promise.resolve()
175+
.then(function() {
176+
// We run our work in here such to allow for asynchronous work
177+
// This example is fully synchronous
178+
return 'hello world';
179+
})
180+
.then(function() {
181+
is_async = false;
182+
});
183+
184+
if (is_async)
185+
console.log('method ran asynchronous');
186+
187+
console.log('method ran synchronous');
188+
189+
/**
190+
* Prints to the console:
191+
* method ran synchronous
192+
*/
193+
```
194+
195+
**Mixed promises**
196+
197+
```javascript
198+
var is_async = true;
199+
200+
Twig.Promise.resolve()
201+
.then(function() {
202+
// We run our work in here such to allow for asynchronous work
203+
return Promise.resolve('hello world');
204+
})
205+
.then(function() {
206+
is_async = false;
207+
});
208+
209+
if (is_async)
210+
console.log('method ran asynchronous');
211+
212+
console.log('method ran synchronous');
213+
214+
/**
215+
* Prints to the console:
216+
* method ran asynchronous
217+
*/
218+
```

0 commit comments

Comments
 (0)