Experience with promises in NPM/Typescript, better options?

8 views
Skip to first unread message

jonathan.s.shapiro

unread,
Jan 19, 2020, 1:01:00 PM1/19/20
to e-lang
This is somewhat off-topic. I've been doing somework in Typescript/NPM of late, and the situation with promises there is vexing. Some of it, clearly, is that promises were a late addition to Javascript rather than a cleanly integrated concept.

Here are the some issues I've hit. I'm looking for pointers to places where things have been done better:

1. Lack of "theory of operation" documentation for promises (no pointers needed). Promises in JS/TS propagate both values and errors. The way in which this propagation is done and how to think about it is not especially well documented, and of course it interacts with the exception model in a very odd way (at least when the surface syntax tries to appear sequential). It is very challenging at first to work out how to construct code in such a way that errors are caught and processed at the right point and in the right way. One ends up building computational hierarchies connected by promises that manifest as deep nesting in the source code.

This is a documentation issue rather than a conceptual issue. The last point probably admits syntactic solutions or remediations. One of the reasons I want to dig back into E.

2. Too many models (no pointers needed) Because JS didn't originally have promises, there are two models of value/error propagation in competition: promises and (value, error) return tuples. This is mainly a consequence of legacy, but it makes connecting libraries and writing practical code a lot more difficult than it needs to be. Of course, some modules are written in strictly sequential/stateful style, so when you're all done it's a bit of a dog's breakfast. This is particularly painful in regards to database access modules at the moment.

3. Initialization Issues (pointers strongly desired). The particular bit of code I was working on made use of several databases. Conceptually, the database connections want to be built before the rest of the program runs, and their are ordering constraints in initialization. In JS/TS the natural way to do this is with globals, but promises are (in effect) unsupported at global scope. One could certainly migrate to a "global state free" idiom, but that makes the source code obscure to programmers who may be unfamiliar with the "no global state" approach. It manifests as a long-term dollar cost for maintenance.

The real issue, I suspect, is that I was trying to use a sequential-model module in an otherwise promise-driven application. I think it's fair to say that whenever two fundamentally different computational models try to speak to each other, the fit is likely to be uncomfortable. :-)

What promise-driven or data dependency driven languages have done a good job with the initialization sequencing issue that I might want to look at?

4. Promise Chains vs Syntax (pointers desired). This is, of course, a known issue with many promise-managed languages. It's mainly, but not completely, a syntactic issue. In essence, all expressions return a (value, error) promise, and the result forwarding in sequential computations ends up more than a little awkward. My sense is that this is largely true because (a) promise resolution/force is not implicit on use, and (b) thread execution order is spaghetti-structured while error handling tends to be hierarchically structured. As an example of the latter, we certainly want to report connection errors and abandon dependent computation before we report that dependent computations failed because the connection was unavailable. From 5,000 feet, the problem is that we're trying to impose [partially] sequential computation on an underlying out-of-order execution model. Another way to say this is that we are commingling control flow dependencies with data flow dependencies.

I am looking for a tolerable syntactic mechanism to deal with this sort of thing. One notion I'm contemplating is a language construct that serves as a "gate" on later computation:

let c = make_new_connection_somehow()

when c resolves
// Computation that performs sanity checks on c
// which might decide to return an error 
}

// Nothing below here that is data dependent on /c/
// proceeds until the guard clause has completed

...Further computation referencing c

In effect this commingles a control flow constraint with a data flow constraint. The underlying implementation can be done by re-binding the names with new promises (in the usual block-structured way) that resolve when the sanity checks complete.

Now that I stare at it, there's a broader pattern issue: there's nothing (that I know about) in the promise idioms that provides a syntactic substitute for the following sequential pattern:

if (required condition not met)
terminate evaluation here returning /mumble/
rest of computation

The absence of a language-level substitute for this idiom seems (to me) to be a big part of what is driving the syntactic need for deeply nested promise chains.
 

I figure I'm either missing something totally obvious here in my understanding of promise/dataflow idioms or this is an actual issue that's probably been handled sanely in some existing language.

What should I look at?

5. Error Propagation Love them or hate them, exceptions have become a well-understood idiom in the wild. I think it's fair to say that JS/TS do not have a comfortable reconciliation of THROW, CATCH, and out-of-order execution. Are there existing languages whose computational model does better? Failing that, are there better error models to use in out-of-order languages? I'd kind of like to preserve the try/catch/finally model for reasons of [human] backwards compatibility, but if it's a square peg in a round hole, what's a better answer?

Pointers appreciated.



Probably more than enough for now. Everything else I've tripped over has pretty obvious syntactic resolutions.


Thanks!


Jonathan
Reply all
Reply to author
Forward
0 new messages