Indolering Indolering - 3 months ago 15
Javascript Question

Optional Promise API

I have a validation library that is purely synchronous but is often used as part of a chain of async functions. However, I have to maintain an existing synchronous API and would like to make the promise API optional. Can I somehow detect (at runtime) whether a function is part of a Promise chain?

This is pretty easy with callbacks since you can just check if a callback was passed in. I understand that I could pass in an optional promise boolean, but that seems inelegant.

I've also considered doing a callback interface and using a library to convert the callback interface to a promise based interface on the fly. However, I'm working in Haxe and I would prefer to keep the transforms/abstractions down to a minimum.

I also understand that you can sandwich regular functions in-between promises, but there are some instances where the behavior would differ between the two.

FINAL Edit Lots of confusion as to why I can't just return the same values, first example (below) didn't seem to help. Remember, this is still simplified.

//mix of sync with promise
new Promise(function(resolve, reject){
var safeToAdd = thingTracker.preflight(newThing);
if(safeToAdd){
return client.request.addThing(newThing); //send request to server
} else {
reject(newThing.errorMessages); //requires explicit reject, cannot just pass results along
}
}).then(function(newThing){ //client and server both cool with newThing?
thingTracker.save(newThing);
}).catch(function(errorMessages){ //handles errorMessages from client and server
ui.show(errorMessages);
});

//pure promise
thingTracker.preflight(newThing).then(function(){
return client.request.addThing(newThing); //sends request to server
}).then(function(newThing){ //client and server both cool with newThing?
thingTracker.save(newThing);
}).catch(function(errorMessages){ //handles errorMessages from client and server
ui.show(errorMessages);
});


(Old) Edit to clarify (but didn't really):

function preflight(thing){
var validity = thing === 42;

if(promise){
if(validity){
return Promise.resolve(validity);
} else {
return Promise.reject(validity);
}
} else {
return validity;
}
}


Obviously I can do the same checks in a
then
's anon function, but that is not much better than using the sync interface directly. Also note that this is a very simple example, in the real function there are side effects on
thing
and messages are produced.

Edit Just to illustrate my point a bit better, here is a gist of what things look like.

Answer

Synchronous functions can be used within a promise chain just fine. If you have a synchronous API call that is called like this:

var info = myApi("foo");

That can be used just fine within a promise chain:

someAsyncCall().then(myApi).then(someOtherFunction)

You don't need to make myApi async or return a promise to be used this way. The only thing you can't do in a promise chain with a synchronous function is that it can't be the first item in the chain, but then it doesn't need to be. Since it's synchronous, it can just be called before you start the chain if anyone wants it to execute first. Or, worst case, you can do:

Promise.resolve().then(myAPI).then(someOtherFunction);

Can I somehow detect (at runtime) whether a function is part of a Promise chain?

No, you cannot (unless you explicitly pass it some info that tells it that) and you do not need to in order to use it in a promise chain. You do not need to return a promise to be a .then() handler. You can just return a value and that value will become the value of the promise chain at that point in the chain.

I also understand that you can sandwich regular functions in-between promises, but there are some instances where the behavior would differ between the two ... such as returning false vs throwing a message.

It is not clear to me why the behavior would have to be different. If returning false is your normal synchronous behavior, you can do that just fine in a promise chain - the next step in the chain just needs to handle the false value that is passed to it, just like the next line of synchronous code would do. If throwing an exception is your normal behavior, you can do that just fine in a promise chain too (the promise chain will turn the exception into a rejection) which the following code can decide how it wants to handle. One can make a good argument that it would be worse if your synchronous function behaved differently based on whether it was part of a promise chain or not. If you see a good reason for two different behaviors, then you should probably have either two different functions (that can be documented differently) or an option passed in that determines the behavior.

Comment based on the code you added in your edit

You seem to think that when called via a promise chain that you need to return a resolved or rejected promise chain. You do not need to do that. You can just return a normal value and the promise chain will inherit the value you return. There is really no reason to return a resolved or rejected promise unless you want it to be the first function in a promise chain, but in that case, it isn't in a promise chain yet anyway so you could never detect that. And, there's no reason for a synchronous operation to be the first one in a promise chain. Just call the synchronous operation first and then initiation your promise chain with the async operations.

Here's a demo of a synchronous function being used in a promise chain:

function log(str) {
    var div = document.createElement("div");
    div.innerHTML = str;
    document.body.appendChild(div);
}

// test async call
function delay(t, val) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve(val);
        }, t);
    });
}

function square(x) {
  return x * x;
}

log("Calculating the square of 9");
delay(500, 9).then(square).then(function(result) {
    log("Result = " + result);
});

Or, you can even do this to initiate a promise chain with your synchronous operation as the first operation in the chain:

function log(str) {
    var div = document.createElement("div");
    div.innerHTML = str;
    document.body.appendChild(div);
}

function square(x) {
  return x * x;
}

log("Calculating the square of 9");
Promise.resolve(9).then(square).then(function(result) {
    log("Result = " + result);
});