Repository: 11ty/eleventy (js)

Issue:
Nunjucks: async shortcode inside paired shortcode
Hi Community!

**Describe the bug**
I want to use an async shortcode inside a paired shortcode. Unfortunately there is no content from the async shortcode (child) if it is inside the paired shortcode (parent). Standalone the async shortcode works. 

**To Reproduce**
I abstracted my code to this:

**Shortcodes (inside template file)**
```
{% parentPairedShortcode %}
  {% asyncChildShortcode %}
{% endparentPairedShortcode %}
```

**Config (.eleventy.js)**
```javascript
async function getSomething() {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("done!"), 1000)
  });
  const result = await promise;
  return result;
}

eleventyConfig.addNunjucksAsyncShortcode("asyncChildShortcode", async function() {
  const something = await getSomething();
  return something;
});

eleventyConfig.addPairedNunjucksAsyncShortcode("parentPairedShortcode", async function(content) {
  return content;
});
```

**Expected behavior**
Get the content of the async shortcode (child) paired shortcode back. I tried that with liquid shortcodes and it works. Did I do something wrong? Or did I forget something?

**Environment:**
- macOS 10.15.4 
- Eleventy v0.10.0

Please let me know if more information is needed. Thank you in advance.

Pull request:
Fix for nested nunjucks async shortcodes
Nested async shortcodes would fail to correctly output content from the inner shortcode as eleventy wasn't waiting for the content to have processed before passing it to the parent.

The `body()` call takes a callback, which gets called when the passed content of a paired shortcode has finished rendering, both on async and sync templates.

Fixes #1053

Patch:
diff --git a/src/Engines/Nunjucks.js b/src/Engines/Nunjucks.js
index 6b0388bd..a1be3fa1 100755
--- a/src/Engines/Nunjucks.js
+++ b/src/Engines/Nunjucks.js
@@ -287,57 +287,68 @@ class Nunjucks extends TemplateEngine {
         var body = parser.parseUntilBlocks("end" + shortcodeName);
         parser.advanceAfterBlockEnd();
 
-        if (isAsync) {
-          return new nodes.CallExtensionAsync(this, "run", args, [body]);
-        }
-        return new nodes.CallExtension(this, "run", args, [body]);
+        return new nodes.CallExtensionAsync(this, "run", args, [body]);
       };
 
       this.run = function (...args) {
-        let resolve;
-        if (isAsync) {
-          resolve = args.pop();
-        }
+        let resolve = args.pop();
         let body = args.pop();
         let [context, ...argArray] = args;
 
-        if (isAsync) {
-          shortcodeFn
-            .call(
-              Nunjucks._normalizeShortcodeContext(context),
-              body(),
-              ...argArray
-            )
-            .then(function (returnValue) {
-              resolve(null, new NunjucksLib.runtime.SafeString(returnValue));
-            })
-            .catch(function (e) {
+        body(function (e, bodyContent) {
+          if (e) {
+            resolve(
+              new EleventyShortcodeError(
+                `Error with Nunjucks paired shortcode \`${shortcodeName}\`${EleventyErrorUtil.convertErrorToString(
+                  e
+                )}`
+              )
+            );
+          }
+
+          if (isAsync) {
+            shortcodeFn
+              .call(
+                Nunjucks._normalizeShortcodeContext(context),
+                bodyContent,
+                ...argArray
+              )
+              .then(function (returnValue) {
+                resolve(null, new NunjucksLib.runtime.SafeString(returnValue));
+              })
+              .catch(function (e) {
+                resolve(
+                  new EleventyShortcodeError(
+                    `Error with Nunjucks paired shortcode \`${shortcodeName}\`${EleventyErrorUtil.convertErrorToString(
+                      e
+                    )}`
+                  ),
+                  null
+                );
+              });
+          } else {
+            try {
+              resolve(
+                null,
+                new NunjucksLib.runtime.SafeString(
+                  shortcodeFn.call(
+                    Nunjucks._normalizeShortcodeContext(context),
+                    bodyContent,
+                    ...argArray
+                  )
+                )
+              );
+            } catch (e) {
               resolve(
                 new EleventyShortcodeError(
                   `Error with Nunjucks paired shortcode \`${shortcodeName}\`${EleventyErrorUtil.convertErrorToString(
                     e
                   )}`
-                ),
-                null
+                )
               );
-            });
-        } else {
-          try {
-            return new NunjucksLib.runtime.SafeString(
-              shortcodeFn.call(
-                Nunjucks._normalizeShortcodeContext(context),
-                body(),
-                ...argArray
-              )
-            );
-          } catch (e) {
-            throw new EleventyShortcodeError(
-              `Error with Nunjucks paired shortcode \`${shortcodeName}\`${EleventyErrorUtil.convertErrorToString(
-                e
-              )}`
-            );
+            }
           }
-        }
+        });
       };
     }
 


Test patch:
diff --git a/test/TemplateRenderNunjucksTest.js b/test/TemplateRenderNunjucksTest.js
index 5738268b..f8884487 100644
--- a/test/TemplateRenderNunjucksTest.js
+++ b/test/TemplateRenderNunjucksTest.js
@@ -545,6 +545,40 @@ test("Nunjucks Async Paired Shortcode", async (t) => {
   );
 });
 
+test("Nunjucks Nested Async Paired Shortcode", async (t) => {
+  t.plan(3);
+
+  let tr = getNewTemplateRender("njk", "./test/stubs/");
+  tr.engine.addPairedShortcode(
+    "postfixWithZach",
+    function (content, str) {
+      // Data in context
+      t.is(this.page.url, "/hi/");
+
+      return new Promise(function (resolve) {
+        setTimeout(function () {
+          resolve(str + content + "Zach");
+        });
+      });
+    },
+    true
+  );
+
+  t.is(
+    await tr._testRender(
+      "{% postfixWithZach name %}Content{% postfixWithZach name2 %}Content{% endpostfixWithZach %}{% endpostfixWithZach %}",
+      {
+        name: "test",
+        name2: "test2",
+        page: {
+          url: "/hi/",
+        },
+      }
+    ),
+    "testContenttest2ContentZachZach"
+  );
+});
+
 test("Nunjucks Paired Shortcode without args", async (t) => {
   let tr = getNewTemplateRender("njk", "./test/stubs/");
   tr.engine.addPairedShortcode("postfixWithZach", function (content) {