{"action":"created","issue":{"url":"https://api.github.com/repos/twigjs/twig.js/issues/457","repository_url":"https://api.github.com/repos/twigjs/twig.js","labels_url":"https://api.github.com/repos/twigjs/twig.js/issues/457/labels{/name}","comments_url":"https://api.github.com/repos/twigjs/twig.js/issues/457/comments","events_url":"https://api.github.com/repos/twigjs/twig.js/issues/457/events","html_url":"https://github.com/twigjs/twig.js/pull/457","id":210941949,"number":457,"title":"Asynchronous rendering and function/filter promises","user":{"login":"JorgenEvens","id":223318,"avatar_url":"https://avatars1.githubusercontent.com/u/223318?v=3","gravatar_id":"","url":"https://api.github.com/users/JorgenEvens","html_url":"https://github.com/JorgenEvens","followers_url":"https://api.github.com/users/JorgenEvens/followers","following_url":"https://api.github.com/users/JorgenEvens/following{/other_user}","gists_url":"https://api.github.com/users/JorgenEvens/gists{/gist_id}","starred_url":"https://api.github.com/users/JorgenEvens/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JorgenEvens/subscriptions","organizations_url":"https://api.github.com/users/JorgenEvens/orgs","repos_url":"https://api.github.com/users/JorgenEvens/repos","events_url":"https://api.github.com/users/JorgenEvens/events{/privacy}","received_events_url":"https://api.github.com/users/JorgenEvens/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2017-03-01T00:12:05Z","updated_at":"2017-03-01T09:07:34Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/twigjs/twig.js/pulls/457","html_url":"https://github.com/twigjs/twig.js/pull/457","diff_url":"https://github.com/twigjs/twig.js/pull/457.diff","patch_url":"https://github.com/twigjs/twig.js/pull/457.patch"},"body":"Allows for functions and filters to return promises and render asynchronously (https://github.com/twigjs/twig.js/issues/61).\r\n\r\nI've tried to build this PR based on the discussions in the original issue and some offline discussions with @Gertt. The currently implementation tries to change as little of the existing code as possible, but I had to restructure the implementation of some `logic`/`expression` code such that it would behave correctly when used with promises.\r\n\r\nOnly the `Twig.expression.parse`, `Twig.expression.resolve`, `Twig.parse` and `Twig.Template.render` methods are truly aware of the fact that promises are used internally. If the flag `allow_async` is set these methods will allow functions and filters to behave asynchronously, otherwise it will throw as soon as the asynchronous behaviour is detected.\r\n\r\nThe original synchronous behaviour of Twig.js is preserved using a internal Promise implementation (`Twig.Promise`) that does not use `setTimeout` to call the `then` methods, in effect running all code synchronously. Since we can mix different implementation of promises (any thenable really), we can return regular promises from functions or filters at which time rendering will start to run asynchronously.\r\n\r\nThe external API should be left unchanged as far as I can tell. To allow for asynchronous rendering a user would use `template.renderAsync` which will return a promise.\r\n\r\nInternally Twig.js should use the `Twig.expression.parseAsync`, `Twig.expression.resolveAsync`, `Twig.parseAsync` and `template.renderAsync` to ensure that every component is ready to receive a promise. These methods are shorthands for an easier transition that take the exact same arguments as the regular methods, but will automatically set the `allow_async` argument to true.\r\n\r\nWhat are your thoughts on this? I'm happy to give this a few revisions if you'd like to try out some different things.\r\n\r\nSome things that might improve readability of this PR:\r\n\r\n- Viewing this PR without indendations highlight (adding `?w=1` to the URL). https://github.com/twigjs/twig.js/pull/457/files?w=1\r\n- [ES6 arrow functions](http://babeljs.io/docs/plugins/transform-es2015-arrow-functions/), less `var that = this` and generally more concise methods inside of `.then(...)`. Depending on your target this would require `babel`."},"comment":{"url":"https://api.github.com/repos/twigjs/twig.js/issues/comments/283284369","html_url":"https://github.com/twigjs/twig.js/pull/457#issuecomment-283284369","issue_url":"https://api.github.com/repos/twigjs/twig.js/issues/457","id":283284369,"user":{"login":"JorgenEvens","id":223318,"avatar_url":"https://avatars1.githubusercontent.com/u/223318?v=3","gravatar_id":"","url":"https://api.github.com/users/JorgenEvens","html_url":"https://github.com/JorgenEvens","followers_url":"https://api.github.com/users/JorgenEvens/followers","following_url":"https://api.github.com/users/JorgenEvens/following{/other_user}","gists_url":"https://api.github.com/users/JorgenEvens/gists{/gist_id}","starred_url":"https://api.github.com/users/JorgenEvens/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JorgenEvens/subscriptions","organizations_url":"https://api.github.com/users/JorgenEvens/orgs","repos_url":"https://api.github.com/users/JorgenEvens/repos","events_url":"https://api.github.com/users/JorgenEvens/events{/privacy}","received_events_url":"https://api.github.com/users/JorgenEvens/received_events","type":"User","site_admin":false},"created_at":"2017-03-01T09:07:34Z","updated_at":"2017-03-01T09:07:34Z","body":"`Twig.async.forEach` will loop over each item in the array, if nothing is returned from the callback, the callback will be immediately invoked for the next iteration. If however the callback returns a promise it will wait until that promise resolves before invoking the next iteration.\r\n\r\nBecause the internal Promise implementation does not use a `setTimeout` to call the next method in the promise chain the entire promise chain should resolve before reaching any [checks](https://github.com/twigjs/twig.js/pull/457/files#diff-22566ec93dc04f3aa4f038de2fd55f29R836) on `is_async` which is set to `false` in the [last](https://github.com/twigjs/twig.js/pull/457/files#diff-22566ec93dc04f3aa4f038de2fd55f29R815) `.then(..)` call.\r\n\r\nAs soon as one of the promises returned from inside of Twig (function / filter) is a regular Promise (not our internal implementation) that promise's internal implementation [will use](https://www.promisejs.org/implementing/) a `setTImeout` (I believe this is required by spec) and `is_async` will not be set to `false` before we reach the check on `is_async`.\r\n\r\nSwitching between the two behaviours is accomplished using the `allow_async` argument that is passed to the different `.parse()` methods. If a template would use an asynchronous function while `allow_async` is not set it will throw an error to notify the user of this.\r\n\r\nThe initial location where `allow_async` is set to true is in the `template.renderAsync` method, or any time any of the `.parseAsync()` shorthands are called.\r\n\r\nSomething I think that might need some clarification after writing this is that `renderAsync` and the `parseAsync` methods might return and resolve a promise **synchronously** due to our internal Promise implementation, but it will **always** return a promise. Only when a truly asynchronous function or filter is used will the promise returned by Twig resolve asynchronously."}}
{"actor":{"display_login":"JorgenEvens"}}