Search code examples
javascriptjquerynode.jspromisealgebraic-data-types

algebraic implementation of $.when in Pacta.js


I'm writing a nodejs thing, and trying the Pacta promise library for fun. Pacta's interface is "algebraic," but I don't have any experience with that paradigm.

I'd like to know what is the "Pacta way" to accomplish the same thing as

$.when.apply(undefined, arrayOfThings)
.then(function onceAllThingsAreResolved(thing1Val, thing2Val, ...) {
    // code that executes once all things have been coerced to settled promises
    // and which receives ordered resolution values, either as 
    // separate args or as a single array arg
}

That is, given an array, an iterator function that returns a promise, and a callback function, I'd like to map the iterator onto the array and provide an array of the resolution values (or rejection reasons) to the callback once all the promises have been settled.

If there isn't an idiomatically algebraic way to express this, I'd be just as interested to know that.

EDIT: updated use of $.when to properly accommodate an array, per @Bergi.


Solution

  • Pacta's interface is "algebraic," but I don't have any experience with that paradigm.

    ADTs are type theory constructs that represent nested data types, like a Promise for Integer. They are heavily used in functional programming, a flavour where you always know the types of your expressions and values. There are no intransparent, implicit type coercions, but only explicit ones.

    This is completely contrary to jQuery's approach, where $.when() and .then() do completely different things based on the types (and number) of its arguments. Therefore, translating your code is a bit complicated. Admittedly, Pacta doesn't have the most useful implementation, so we have to use some own helper functions to do this.

    • Assume you have an array of (multiple) promises, and your then callback takes the arguments and returns a non-promise value:

      arrayOfPromises.reduce(function(arr, val) {
          return arr.append(val);
      }, Promise.of([])).spread(function (…args) {
          // code that executes once all promises have been fulfilled
          // and which receives the resolution values as separate args
      });
      
    • If your callback does not take multiple arguments, use map instead of spread:

      arrayOfPromises.reduce(function(arrp, valp) {
          return arrp.append(valp);
      }, Promise.of([])).map(function (arr) {
          // code that executes once all promises have been fulfilled
          // and which receives the resolution values as an array
      });
      
    • If your callback does return a promise, use chain instead of map:

      arrayOfPromises.reduce(function(arr, val) {
          return arr.append(val);
      }, Promise.of([])).chain(function (arr) {
          // code that executes once all promises have been fulfilled
          // and which receives the resolution values as an array
      });
      

      If you don't know what it returns, use then instead of chain. If you don't know what it returns and want to get multiple arguments, use .spread(…).then(identity).

    • If your array contains promises mixed with plain values, use the following:

      arrayOfThings.reduce(function(arrp, val) {
          var p = new Promise();
          Promise.resolve(p, val);
          return arrp.append(p);
      }, Promise.of([])).…
      
    • If your array contains only a single or no (non-thenable) value, use

      Promise.of(arrayOfThings[0]).…
      
    • If your array contains anything else, even $.when would not do what you expect.

    Of course, promises that resolve with multiple values are not supported at all - use arrays instead. Also, your callback will only be called when all promises are fulfilled, not when they're settled, just as jQuery does this.