Repository: mozilla/nunjucks (js)

Issue:
env.render() invokes the callback twice
Node: 6, 7
Nunjucks: 3.0.1
OS: Win 10x64

Under [specific circumstances](/Spown/nunj_double_rainder), when a templating error occurs the callback of the ``.render()`` method runs twice: one for the error and one more time returning an incomplete output. In the test case provided I took my project and reduced it to a minimum condition for this to happen.  Just run it and the output should show something like this
```
sending for webpage content
render #1 
 error is: 
 Template render error: (d:\Projects\nunj_double_rainder\tmpl\views\page_view.html)
  Template render error: (d:\Projects\nunj_double_rainder\tmpl\mixins\_header.html)
  Template render error: (d:\Projects\nunj_double_rainder\tmpl\mixins\_register_auth.html)
  Error: Unable to call `map_elements["nonExistantMacro"]`, which is undefined or falsey 
 rendered content is: 
 undefined
render #2 
 error is: 
 null 
 rendered content is: 
 Layout
 |- [<- extends] Serial Layout:
    |- [<- includes] _Header:
      |- [<- includes] _register_auth:
    |- [<- extends] page_view
    |/_Header
 |/Serial Layout
/Layout
express app.send(): Error: Can't set headers after they are sent.
got webpage content length: <h1>U FAIL!</h1><div>Reason: Template render error: (d:\Projects\nunj_double_rainder\tmpl\views\page_view.html)
  Template render error: (d:\Projects\nunj_double_rainder\tmpl\mixins\_header.html)
  Template render error: (d:\Projects\nunj_double_rainder\tmpl\mixins\_register_auth.html)
  Error: Unable to call `map_elements["nonExistantMacro"]`, which is undefined or falsey</div>
```

Here the second callback run yelds a text, where it lacks `` |- [<- import] map_elements:`` after ``|- [<- includes] _register_auth:`` from 
[here](/Spown/nunj_double_rainder/blob/65748ded18d563dd8846fbfb8feb13d5b1c5540f/tmpl/mixins/_register_auth.html#L2)

Further investigation has shown that if I unwrap [this](/Spown/nunj_double_rainder/blob/65748ded18d563dd8846fbfb8feb13d5b1c5540f/tmpl/mixins/_header.html#L1) (if I remove the ``if`` check) - the whole construction starts to work as it should.

This error may be a very circumstantial, but it is also very serious, since render callback often contains the ``express``'s ``.send()`` method and it throws exception if you try to send the second time, potentially crashing the whole app, not just failing to render one page. If not caught that is...

I tried to traceback it but some parts seem to be async and there is just too much VM noise so I loose the track of the callback after going like 20 steps deep into ``.render()``

Pull request description:
Fix multiple callback calls, issue #1029
## Summary

Proposed change:

Fix an issue where an error thrown from a conditionally included template will call the provided callback twice. See the included test for details; fails against master but passes with this change.

Closes #1029 


## Checklist
* [x] [*Tests*](https://github.com/mozilla/nunjucks/tree/master/tests) are added / updated to cover proposed change.
* [x] [*Changelog*](https://github.com/mozilla/nunjucks/blob/master/CHANGELOG.md) has an entry for proposed change (if user-facing fix or feature).

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f0897c..42096cb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,8 +5,11 @@ Changelog
 master (unreleased)
 -------------------
 
+* Fix calling render callback twice when a conditional import throws an error.
+  Solves [#1029](https://github.com/mozilla/nunjucks/issues/1029)
+
 * Support objects created with Object.create(null). fixes [#468](https://github.com/mozilla/nunjucks/issues/468)
-  
+
 
 3.0.1 (May 24 2017)
 -------------------
diff --git a/src/environment.js b/src/environment.js
index 413e66c..1ffa820 100644
--- a/src/environment.js
+++ b/src/environment.js
@@ -152,12 +152,12 @@ var Environment = Obj.extend({
         }
         return this.filters[name];
     },
-    
+
     addTest: function(name, func) {
         this.tests[name] = func;
         return this;
     },
-    
+
     getTest: function(name) {
         if(!this.tests[name]) {
             throw new Error('test not found: ' + name);
@@ -495,6 +495,7 @@ Template = Obj.extend({
         var frame = parentFrame ? parentFrame.push(true) : new Frame();
         frame.topLevel = true;
         var syncResult = null;
+        var didError = false;
 
         _this.rootRenderFunc(
             _this.env,
@@ -502,8 +503,13 @@ Template = Obj.extend({
             frame || new Frame(),
             runtime,
             function(err, res) {
+                if (didError) {
+                    // prevent multiple calls to cb
+                    return;
+                }
                 if(err) {
                     err = lib.prettifyError(_this.path, _this.env.opts.dev, err);
+                    didError = true;
                 }
 
                 if(cb) {


Test patch:
diff --git a/tests/api.js b/tests/api.js
index 7c10d95..6860b9a 100644
--- a/tests/api.js
+++ b/tests/api.js
@@ -44,6 +44,17 @@
             expect(util.normEOL(test.render())).to.be('Test1\nTest2');
         });
 
+        it('should only call the callback once when conditional import fails', function(done) {
+            var env = new Environment(new Loader(templatesPath));
+            var called = 0;
+            env.render('broken-conditional-include.njk',
+              function() {
+                expect(++called).to.be(1);
+              }
+            );
+            setTimeout(done, 0);
+        });
+
         it('should handle correctly relative paths in renderString', function() {
             var env = new Environment(new Loader(templatesPath));
             expect(env.renderString('{% extends "./relative/test1.njk" %}{% block block1 %}Test3{% endblock %}', {}, {
diff --git a/tests/templates/broken-conditional-include.njk b/tests/templates/broken-conditional-include.njk
new file mode 100644
index 0000000..a606594
--- /dev/null
+++ b/tests/templates/broken-conditional-include.njk
@@ -0,0 +1,3 @@
+{% if not whatever %}
+    {% include "throws.njk" %}
+{% endif %}
diff --git a/tests/templates/throws.njk b/tests/templates/throws.njk
new file mode 100644
index 0000000..4ed097a
--- /dev/null
+++ b/tests/templates/throws.njk
@@ -0,0 +1,1 @@
+{{ nonExistentFn() }}