Marko: Output of render should be Promise-compatible

Created on 20 Mar 2016  路  7Comments  路  Source: marko-js/marko

Currently rendering a template returns an AsyncWriter, it would be handy though if when a callback is omitted that templates render function returned a promise.

var template = require("template.marko");

template.render({ hello: "world" })
    .then(function (html) { ... })
    .catch(function (err) { ... });

If you're using promises already it makes using marko a bit easier.

good first issue runtime feature

Most helpful comment

I'd rather modify async-writer to be a thenable by adding .then and .catch

I've seen other libraries such as superagent and mongoose implement a promise-like interface that plays well with anything that is duck-typing rather than looking at instanceof Promise (which is most everything due to things like bluebird and q).

It would support both

template.render({ hello:"world" })
    .then(function (html) { ... })
    .catch(function (err) { ... });

and

template.render({ hello:"world" })
    .on('error', function(e) { ... })
    .on('finish', function() { ... })

And with async/await you'd be able to do:

var html = await template.render({ hello:"world" })

We could also implement a .promise function (or some other name) that returns a true Promise for cases where it is needed.


Expand for possible implementation

{
    then(fn, fnErr) {
        var promise = new Promise((resolve, reject) => {
            var buffer = ''
            this.on('data', data => buffer += data)
            this.on('error', error => reject(error))
            this.on('finish', () => {
                try {
                    resolve(fn(buffer))
                } catch(err) {
                    reject(err)
                }
            })
        })
        if(fnErr) {
            promise = promise.catch(fnErr)
        }
        return promise
    }
    promise() {
        return this.then(x => x)
    }
    catch(fn) {
        return this.promise().catch(fn)
    }
}

All 7 comments

You can use temporarily bluebird module.

let Promise = require('bluebird');
let EntryCommentsMarkup = require('./views/EntryComments.marko');
Promise.promisifyAll(EntryCommentsMarkup);

then in routing we invoke renderAsync method:

app.get('/example', function(req, res) {
    let Data = { ... };
    return EntryCommentsMarkup.renderAsync(Data)
        .then((markup) => res.status(200).json({ markup: markup }))
        .catch((error) => res.status(400).json({ message: error.message }));
});

/CC @philidem @mlrawlings

I would like to see a method that returns a promise, but I would prefer to avoid a breaking change. The purpose of returning the AsyncWriter instance was to be compatible with the EventEmitter API (on, once, emit, etc.) that AsyncWriter implements. For example:

template.render({ hello: "world" })
    .on('foo', function(e) {

    })
    .on('error', function(e) {

    })
    .on('finish', function() {

    })

What are your all thoughts on utilizing the native promise implementation to provide a new renderAsync method?:

var template = require("template.marko");

template.renderAsync({ hello: "world" })
    .then(function (html) { ... })
    .catch(function (err) { ... });

I'd rather modify async-writer to be a thenable by adding .then and .catch

I've seen other libraries such as superagent and mongoose implement a promise-like interface that plays well with anything that is duck-typing rather than looking at instanceof Promise (which is most everything due to things like bluebird and q).

It would support both

template.render({ hello:"world" })
    .then(function (html) { ... })
    .catch(function (err) { ... });

and

template.render({ hello:"world" })
    .on('error', function(e) { ... })
    .on('finish', function() { ... })

And with async/await you'd be able to do:

var html = await template.render({ hello:"world" })

We could also implement a .promise function (or some other name) that returns a true Promise for cases where it is needed.


Expand for possible implementation

{
    then(fn, fnErr) {
        var promise = new Promise((resolve, reject) => {
            var buffer = ''
            this.on('data', data => buffer += data)
            this.on('error', error => reject(error))
            this.on('finish', () => {
                try {
                    resolve(fn(buffer))
                } catch(err) {
                    reject(err)
                }
            })
        })
        if(fnErr) {
            promise = promise.catch(fnErr)
        }
        return promise
    }
    promise() {
        return this.then(x => x)
    }
    catch(fn) {
        return this.promise().catch(fn)
    }
}

I like the suggestion to implement the then and catch methods on the AsyncWriter instance. Let's go forward with that approach.

FYI: it should be sufficient to add the then() and catch() methods to OutMixins.js since those mixins are added to both AsyncStream and AsyncVDOMBuilder

NOTE: We want to follow the rules for having a consistent rendering API: https://github.com/marko-js/marko/issues/389

That is:

template.render({})
    .then(function(out) {
        out.appendTo(document.body);
    });

A bit late, just throwing this out there: The Promises/A+ specification requires that if the result is an object or function with a then method, it _must_ be treated as a "thenable". In other words, this solution is explicitly permitted by the specification, and any Promise library which doesn't handle this correctly is - by definition - "broken". That's all, carry on. :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

basickarl picture basickarl  路  4Comments

basickarl picture basickarl  路  3Comments

DavidBernal picture DavidBernal  路  5Comments

JarvisPrestidge picture JarvisPrestidge  路  4Comments

mauricionr picture mauricionr  路  4Comments