diff --git a/runtime/OutMixins.js b/runtime/OutMixins.js index b0a99c417..2de621843 100644 --- a/runtime/OutMixins.js +++ b/runtime/OutMixins.js @@ -71,6 +71,29 @@ module.exports = { return this; }, + then: function(fn, fnErr) { + var self = this; + var promise = new Promise(function(resolve, reject) { + self.on('error', reject); + self.on('finish', function(data) { + try { + resolve(fn(data)); + } catch(err) { + reject(err); + } + }); + }); + + if (fnErr) { + promise = promise.catch(fnErr); + } + return promise; + }, + + catch: function(fnErr) { + return this.then(undefined, fnErr); + }, + appendTo: function(referenceEl) { var newNode = this.getNode(referenceEl.ownerDocument); dom.appendTo(newNode, referenceEl); diff --git a/runtime/html/AsyncStream.js b/runtime/html/AsyncStream.js index 7a3f58d67..427002817 100644 --- a/runtime/html/AsyncStream.js +++ b/runtime/html/AsyncStream.js @@ -223,7 +223,7 @@ var proto = AsyncStream.prototype = { if (state.writer.end) { state.writer.end(); } else { - state.events.emit('finish'); + state.events.emit('finish', this); } } } @@ -292,7 +292,7 @@ var proto = AsyncStream.prototype = { var state = this._state; if (event === 'finish' && state.finished) { - callback(); + callback(this); return this; } @@ -304,7 +304,7 @@ var proto = AsyncStream.prototype = { var state = this._state; if (event === 'finish' && state.finished) { - callback(); + callback(this); return this; } diff --git a/test/AsyncStream-test.js b/test/AsyncStream-test.js index 4ca8ebf46..946632605 100644 --- a/test/AsyncStream-test.js +++ b/test/AsyncStream-test.js @@ -57,6 +57,19 @@ describe('async-writer' , function() { }); }); + it('should resolve promise upon finish', function() { + var out = new AsyncStream(); + + out.write('1'); + out.write('2'); + + return out.end().then((data) => { + const output = out.getOutput(); + expect(output).to.equal('12'); + expect(data.getOutput()).to.equal('12'); + }); + }); + it('should render a series of sync and async calls correctly', function(done) { var out = new AsyncStream(); out.write('1'); @@ -250,6 +263,46 @@ describe('async-writer' , function() { }); }); + it('should catch error in promise catch', function(done) { + const out = new AsyncStream(); + + let errors = []; + + out.on('error', function(e) { + errors.push(e); + }); + + out.write('1'); + + let asyncOut = out.beginAsync(); + setTimeout(function() { + asyncOut.error(new Error('test')); + }, 10); + + out.write('3'); + out.end().catch((err) => { + expect(errors.length).to.equal(1); + expect(err).to.be.an('error'); + done(); + }); + }); + + it('should catch error in promise catch if `error` listener only set inside mixin', function(done) { + const out = new AsyncStream(); + + out.catch((err) => { + expect(err).to.be.an('error'); + expect(out.getOutput()).to.equal('1'); + done(); + }).then((data) => { + throw new Error('Should not get here!'); + }); + + out.write('1'); + out.error(new Error('test')); + out.write('2'); + }); + it('should support chaining', function(done) { var errors = []; var out = new AsyncStream() diff --git a/test/AsyncVDOMBuilder-test.js b/test/AsyncVDOMBuilder-test.js index da7692e4d..3c8019bf4 100644 --- a/test/AsyncVDOMBuilder-test.js +++ b/test/AsyncVDOMBuilder-test.js @@ -41,6 +41,15 @@ it('async', function(done) { }); }); +it('promise', function(done) { + const out = new AsyncVDOMBuilder(); + out.element('div', {}, 0); + out.end().then((tree) => { + expect(tree.childNodes.length).to.equal(1); + done(); + }); +}); + it('async flush', function(done) { var out = new AsyncVDOMBuilder(); out.on('update', function(tree) { diff --git a/test/autotests/api/load-render-promise/template.marko b/test/autotests/api/load-render-promise/template.marko new file mode 100644 index 000000000..eabd502aa --- /dev/null +++ b/test/autotests/api/load-render-promise/template.marko @@ -0,0 +1 @@ +- Hello ${data.name}! \ No newline at end of file diff --git a/test/autotests/api/load-render-promise/test.js b/test/autotests/api/load-render-promise/test.js new file mode 100644 index 000000000..656f6e105 --- /dev/null +++ b/test/autotests/api/load-render-promise/test.js @@ -0,0 +1,16 @@ +'use strict'; + +const nodePath = require('path'); + +exports.check = function(marko, markoCompiler, expect, done) { + let template = marko.load(nodePath.join(__dirname, 'template.marko')); + + template.render({ + name: 'John' + }).then((out) => { + expect(out.getOutput()).to.equal('Hello John!'); + done(); + }).catch((err) => { + done(err); + }); +}; \ No newline at end of file