Conversation
|
I recently spent two weeks attempting to integrate jsonata queries into handlebars.js templates and did not have a good time. handlebars.js have made a decision to remain synchronous, with the reasonable argument that: "We're doing a synchronous operation, why should we run asynchronusly? Run your long running operations first." ... They have a point. Handlebars.js is not an executor. It really is a synchronous operation. Their focus was on template rendering performance and they weren't going to accept any degradation for a use case that isn't in scope. The challenge is that many libraries, jsonata.js among them, have chosen to be async, making integration basically impossible. Interestingly, both projects faced the same question: "what kind of custom functions should we accept?" and arrived at opposite answers:
I've reviewed @mmkal's work here and I agree with them, that is a dirty hack. But hey it passes the test suites. There's a similarly dirty hack on the handlebars.js side where someone modified the template compiling code to just add async awaits to everything. That actually does break some things, but also people are using it in production environments right now because people are trying to integrate things like this. The taint of async spreads to all that it touches unfortunately, and it's no easy task to contain it. I would not blame the maintainers for rejecting this pull request. But the need for it really is there, or at least I think so... and in defense of @mmkal's work. If the sync version really passes all the tests. And it's still not acceptable for release, then is it the hack that's the problem? or are tests incomplete? |
|
While we wait for an answer on this, I've published my version to npm under @mmkal/jsonata: import jsonata from "@mmkal/jsonata/sync";
console.log(
jsonata('foo.bar').evaluate({ foo: { bar: 123 } })
) |
Hi 👋
We're using jsonata to allow our customers to write custom matchers for the state of an object. As it stands right now, this means the place where we call
jsonata(customerThing.expression).evaluate(ourData)we have to addawait. Which means that function has to become async, and we ideally don't want that. We don't want to enable any custom functions, and certainly not async ones. So we'd prefer to have a synchronous version of jsonata. And looking at the code, I noticed that almost none of it needs to be asynchronous for any reason other than it might call some asynchronous user-defined function.So, this PR uses a really dumb script to create a whole separate submodule of the package (under
./sync) which:asyncandawaitwith/* async */and/* await */Promise.all(...)withArray.from(...)(pointless calling Array.from on something that's already an array, but avoids complicated syntax modificationif (isPromise(result)) { ... }withif (isPromise(result)) throw new Error('no promises pls')It also copies over all tests, modifying them to use the sync version of jsonata instead:
async-function.jstests which are specifically for time-based and user-defined async functionNot sure if you'll want to accept and maintain this - the implementation right now is a hack. But it seems pretty valuable to provide an API that doesn't require calling from an async function. So, opening a PR if for no other reason than as a PSA - it can be used right now, at least from node, by adding
"jsonata": "github:mmkal/jsonata#syncify.js"to package.json.Note: a library like quansync might be a good option for supporting both sync and async without having to duplicate the code.