Why are promises complex? Because they complect a lot of semantics into a single operation.
define: complect
be interwoven or interconnected; "The bones are interconnected via the muscle".
Complection means that many different operations are interwoven and interconnected into a single thing.
In the case of promises, this single thing is .then()
Say you want to transform the value within a promise. This should be a simple operation.
Let's say we have a promise for a HttpResponse and we want to create a promise for the body.
var body = map(response, function (response) {
return response.body
})map is very simple to implement.
function map(promise, lambda) {
return new Promise(function (resolve, reject) {
promise.then(function (x) { resolve(lambda(x)) }, reject)
})
}Say you want to asynchronously transform the value within a promise.
This is also a simple operation. Let's say we want to stat a file and then read it.
var file = chain(stat(file), function (stat) {
return read(file)
})chain is very simple to implement.
function chain(promise, lambda) {
return new Promise(function (resolve, reject) {
promise.then(function (value) {
lambda(value).then(resolve, reject)
}, reject)
})
}//TODO
function either(promise, recover, lambda) {
return new Promise(function (resolve, reject) {
promise.then(function (value) {
lambda ? lambda(value).then(resolve, reject) : resolve(value)
}, function (error) {
recover(error).then(resolve, reject)
})
})
}//TODO
function cache(promise) {
var cached, resolves, rejects
return new Promise(function (resolve, reject) {
if (cached) {
return (cached.v ? resolve(cached.v) : reject(cached.e))
} else if (resolves) {
return resolves.push(resolve), rejects.push(reject)
}
resolves = [resolve], rejects = [reject]
promise.then(function (value) {
cached = { v: value }
resolves.forEach(function (r) { r(value) })
}, function (error) {
cached = { e: error }
rejects.forEach(function (r) { r(error) })
})
})
}In the recommended usage pattern for interacting with promises you just use then() for all
four of these use cases. A single method overloaded to support all of them.
Each one of these operations is very easy to write (<10 loc). Yet for some reason the popular approach is to complect them into a single operation?
function Promise(handler) {
return { then: function (f, r) { handler(f, r) } }
}Combined with the primitives for sync (map) / async transformation (chain), error handling (either) and shared computation (cache). We can have a full promise implementation in a mere 50 lines.
This is also illustrates that the greatest amount of complexity lies in shared computation because it actually has to deal with shared state and that is complex.
To make things worse, there is talk of extending the already incredibly complex and complected operation of .then() to
incorporate progress events, which means it should be able to handle "streaming" use-cases.
Streams are incredibly complex in their own right. The union of { map, chain, either, cache } and all stream operations
in a single .then() method sounds pretty crazy.
thenisn't really part of what a promise is, its an abstraction on top of promises, it comes bundled with most implementations but is still a separate concept. Its job is to connect computations. thats all so I don't see it as complected. The promise is the thing that represents the computation. Its a proxy. A pure promise would have three methodsread,write, anderrorwhich pretty much do what they say.thenshould be built on top ofreadinstead most implementations only offerthenbecause it happens to make an OKread. I think it kind of sucks too that all promise specs define themselves in terms ofthensince when it comes time to inter-oping with each other they all treatthenasreadanyway. lolMy definitions:
then: connects computations