From 2de6036ae8a9e27d2521eec3ff17a31677f0004f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ku=CC=88hl?= Date: Wed, 27 Dec 2017 08:16:50 +0100 Subject: [PATCH 1/3] Ensure children are processed properly in async mode Add a test that renders a non-blocking element including an actual asynchronous child element. We should ensure that elements of this kind are ordered and nested properly. --- test/test_renderer.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/test_renderer.js b/test/test_renderer.js index 05943ee..ed09325 100644 --- a/test/test_renderer.js +++ b/test/test_renderer.js @@ -42,6 +42,18 @@ describe("renderer", _ => { done(); }); + it("should support non-blocking mode", done => { + let { renderer, stream } = setup(); + + /* eslint-disable indent */ + renderer.renderView(NonBlockingContainer, null, stream, { fragment: true }, _ => { + assert.equal(stream.read(), + "

lorem ipsum

"); + done(); + }); + /* eslint-enable indent */ + }); + it("should detect non-blocking child elements in blocking mode", done => { let { renderer, stream } = setup(); From afdb4b9b9062c1098a41a51a9ac4acbfa42037f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ku=CC=88hl?= Date: Wed, 27 Dec 2017 10:38:17 +0100 Subject: [PATCH 2/3] Use recursion to process child element generators in async mode Element generators can have acynchronous children in non-blocking mode, in which case the their _siblings_ should only be processed _after_ the current child has executed its callback. This is only relevant in non-blocking mode because in blocking mode we are certain that the children have run their callbacks once they have returned. --- src/html.js | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/html.js b/src/html.js index bd90b2b..2766324 100644 --- a/src/html.js +++ b/src/html.js @@ -93,23 +93,31 @@ function processChildren(stream, children, options, callback) { continue; } - let { nonBlocking, log, _idRegistry } = options; - let generatorOptions = { nonBlocking, log, _idRegistry }; - if(child.length !== 1) { // element generator -- XXX: brittle heuristic (arity) - child(stream, generatorOptions, callback); - continue; - } - - // deferred child element - let fn = element => { - element(stream, generatorOptions, callback); + let _callback = function() { + let res = callback.apply(null, arguments); let next = i + 1; if(next < children.length) { let remainder = children.slice(next); processChildren(stream, remainder, options, callback); } + return res; }; + let { nonBlocking, log, _idRegistry } = options; + let generatorOptions = { nonBlocking, log, _idRegistry }; + if(child.length !== 1) { // element generator -- XXX: brittle heuristic (arity) + if(nonBlocking) { + child(stream, generatorOptions, _callback); + break; + } else { + child(stream, generatorOptions, callback); + continue; + } + } + + // deferred child element + let fn = element => element(stream, generatorOptions, _callback); + if(!nonBlocking) { // ensure deferred child element is synchronous let invoked = false; From 58d7e99bfa43b942aa4e7a5985390d0cb6b8d9f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ku=CC=88hl?= Date: Wed, 27 Dec 2017 14:48:03 +0100 Subject: [PATCH 3/3] Use process.nextTick to reset our stack in non-blocking mode --- src/html.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/html.js b/src/html.js index 2766324..8982ec7 100644 --- a/src/html.js +++ b/src/html.js @@ -98,7 +98,12 @@ function processChildren(stream, children, options, callback) { let next = i + 1; if(next < children.length) { let remainder = children.slice(next); - processChildren(stream, remainder, options, callback); + if(nonBlocking) { + process.nextTick(_ => + processChildren(stream, remainder, options, callback)); + } else { + processChildren(stream, remainder, options, callback); + } } return res; };