UPDATE: This library is now deprecated!! Please read here for more details:
https://github.com/cleod9/post-scriptum
So I released another JavaScript library recently called PostScriptum.js (or PS for short) that provides a mechanism of managing asynchronous callbacks. It resembles a promises library in terms of syntax, but is far from the Promises/A+ specification. It basically acts as a utility to allow you to write asynchronous chains that can, contrary to traditional promises, share data between them like this:
PS.create(authenticateUser('bob', 'pass123'), function (user) { this.proceedWith([user], [user.user_id]) }).pthen(getUserPosts, function (user, posts) { console.log(user.name + ' has ' + posts.length + ' posts.'); }).run();
The code above demonstrates an asynchronous API call for logging in a user, and using the obtained user ID to fetch their posts.
But you still might be wondering, what makes this any better than a regular promise library?
They key difference here is the control-flow of your asynchronous calls. In a typical “promise” flow the moment you execute a promise function, it is queued to start at the beginning of the next JavaScript event loop tick. While this works just fine in simple use cases, it ends up restricting you to a promise-specific workflow. All of your functions that rely on asynchronous callbacks need to be wrapped by a promise library in order make them chainable, and you are offered no additional constructs to share data between promises (resulting in the reliance on hoisted variables that share data). To give a quick example, take a look again at the code I posted above using traditional promise syntax:
var shared = {}; authenticateUser('bob', 'pass123') .then(function (user) { shared.user = user; return getUserPosts(user.user_id); }).then(function (posts) { console.log(shared.user.name + ' has ' + posts.length + ' posts.'); });
Looks similar, no? But take a closer look at the usage of the shared variable. Without it, we wouldn’t have a way to access the user anymore within the subsequent then() call. This is because regular promises can only pass one argument to the then() callback, and that argument is always the return value of a promise. As a result, any promises you create can only return one piece of information. You have to pass around extra redundant information with each promise via an object, whether it be through the promise return value itself or as a shared object like the example above. Of course you could alternatively nest the second then() call within the first, but in my opinion doing that would defeat the purpose of promises as a tool for flattening asynchronous callbacks.
Another difference is that normal promise libraries don’t require you to execute a run() function. While it is definitely useful to have promises be queued up to execute right away, I chose not to do this in PS due to the compositional nature of the library. PS allows you to compose your asynchronous execution flow before actually executing anything. I personally found that adding explicit developer intent behind the execution made this type of code easier to follow by separating definition from implementation, and I figured that 6 extra characters couldn’t hurt. It also only needs to be called once on the root PS instance and you’re good to go from there.
In addition, PS by default does not queue your asynchronous call to the next tick of the event loop. Instead, it assumes the function you provided is already asynchronous and executes it immediately. The reason for this is that I figured if you’re already presumably using the library with asynchronous functions, why forcibly stack an extra setTimeout() on top of everything? If you want the function you called to be executed on the next event loop tick you can use a flag for that. This library doesn’t attempt to forcibly change how the libraries you are using currently behave.
The final major difference is that PS by default does not consume errors. The way promises are required to consume errors is my biggest pet peeve about them, in that they tend to obliterate useful details about how and where an error occurred. There is nothing worse than trying to debug a library that consumed all of your error output, so I decided it would make more sense to trust the developer to write proper error handling for their asynchronous calls in the first place. Sure it helps if you have a thorough understanding of how promises work, but it doesn’t make sense to me that we would even allow this to be possible in the first place without explicit developer intent. If you need to capture errors with PS you can do it via the catch() function, or by using the pthen() function (see the documentation for details on that)
Anyway, I know that this article might seem very “anti-promises”, but I should make it clear that I do think there’s definitely a place in JavaScript for promises. Traditional promises can definitely make callback chains cleaner in many cases, but they do not address the main cause of callback hell for me personally which was the need to exchange information between callbacks. I created PostScriptum.js for devs who want an alternative to promises without too much abstraction, and simple mechanisms to pass data along callback chains. Promises definitely still have a place in the world, but I don’t think it can hurt to have an alternative out there.
Be First to Comment