0

I am trying to rewrite a synchronous junction to work with promises, and am a bit stuck with it. I have a function that call different routines A, B, and C, depending on arguments and results:

const worker = (v, r, ok, er)=>{
  if(v > 10) {
    ok(r)
  } else {
    er(r)
  }
};

const A = v=>{let r = null; worker(v, 'a', _r=>r=_r, ()=>{});return r};
const B = v=>{let r = null; worker(v, 'b', _r=>r=_r, ()=>{});return r};
const C = v=>{let r = null; worker(v, 'c', _r=>r=_r, ()=>{});return r};

const mainSync = (obj)=>{
  let result = null;
  if(obj.a) {
    result = A(obj.a);
  }
  if (!result && obj.b) {
    result = B(obj.b);
  }
  if (!result && obj.c) {
    result = C(obj.c);
  }
  return result;
}

which works fine https://repl.it/JcjE/0 with synchronous A, B, and C:

mainSync({a:4}) === null;
mainSync({a:4, b:14}) === 'b';
mainSync({a:14, b:14}) === 'a';
mainSync({b:4, c:14}) === 'c';
// etc

Now A, B, and C become Promises:

const worker = (v, r, ok, er)=>{
  if(v > 10) {
    ok(r)
  } else {
    er(r)
  }
};

const A = v=>new Promise((ok, er)=>worker(v, 'a', ok, er));
const B = v=>new Promise((ok, er)=>worker(v, 'b', ok, er));
const C = v=>new Promise((ok, er)=>worker(v, 'c', ok, er));

and I am not quite sure how to handle it:

const mainAsync = (obj)=>{
    // what todo here?
}

I am happy with mainAsync to return Promise itself, like

mainAsync({a:4}).then(r=>r === null);
mainAsync({a:4, b:14}).then(r=>r === 'b');
mainAsync({a:14, b:14}).then(r=>r === 'a');
mainAsync({b:4, c:14}).then(r=>r === 'c');

The problem is that call to B depends on result of A, and call to C depends on results of both A and B, and no async/await available yet.

I have tried my naive approach https://repl.it/Jcjw/0 but it is terrible and doesn't quite work on real life scale.

PS: I am looking for vanilla javascript if possible, and am aware about similar questions like

etc, but couldn't figure out how to apply them to my case.

Alex Blex
  • 34,704
  • 7
  • 48
  • 75
  • 2
    Why would you even be attempting to use promises for synchronous operations? Is this just a learning effort? Because there's no practical reason to use promises with purely synchronous operations - that just makes code more complicated than it needs to be. – jfriend00 Jul 21 '17 at 18:03
  • No, it is quite practical question. Functions A B C have been changed and now I have to adjust my code to use them. – Alex Blex Jul 21 '17 at 18:12
  • 1
    I still don't understand. There's just no reason to use promises if all code is synchronous. Just return a value from a function like all normal synchronous code. – jfriend00 Jul 21 '17 at 18:14
  • Sorry if I wasn't clear, but functions A, B and C are beyond my control. In the new version they became promises. – Alex Blex Jul 21 '17 at 18:20
  • 1
    Yeah, that was not clear at all. Please edit your question to make that clear. Also, you don't "call promises" so that phrasing is confusing. You call functions that return promises. A promise is an object that represents some future asynchronous result (or error) - it is not a function. – jfriend00 Jul 21 '17 at 18:22
  • Are you just looking for `A().then(B).then(C)` to sequence your three async functions and pass prior results to the next step? – jfriend00 Jul 21 '17 at 18:27
  • @jfriend00 thanks for your comments, I will edit the question. The chain wouldn't work like this as B should be called on `catch`, not on `then`, and A may not be called at all if obj.a argument is not provided. It is exactly what I have tried, and I don't like how it looks. – Alex Blex Jul 21 '17 at 18:38
  • Well, there's too much pseudo code for me to follow what the real problem is. Perhaps more words of explanation and less pseudo code would explain what you're really trying to accomplish. Try writing a spec in words for what you want to happen. – jfriend00 Jul 21 '17 at 18:41
  • @jfriend00, it's not a pseudo code, but stripped down mcve. I have provided repl.it linls for both sync and async versions. I'll try to explain it English, if it helps. – Alex Blex Jul 21 '17 at 18:49

4 Answers4

1

Depending on which type of browsers you are trying to target, you can use the async/await features that are in chrome for sure.

function promiseA(){return Promise.resolve(20)}
function promiseB(arg){return Promise.resolve(arg * 2)}
function promiseC(arg){return Promise.resolve(arg + 10)}

(async function(){
  let a = await promiseA();
  console.log(a)
  let b = await promiseB(a);
  console.log(b)
  let c = await promiseC(b);
  console.log(c)
})();
Get Off My Lawn
  • 34,175
  • 38
  • 176
  • 338
  • Note that this is ES7 ( so it may not fall under the ES2015 condition) – Jonas Wilms Jul 21 '17 at 18:02
  • @Jonasw I noticed that this has a node tag, and the new version of node now supports async/await – Get Off My Lawn Jul 21 '17 at 18:08
  • Yeah its all fine, just wanted to point it out, may be useful for the OP – Jonas Wilms Jul 21 '17 at 18:09
  • Yeah I figured, I also wanted to point out its in the new version of node. – Get Off My Lawn Jul 21 '17 at 18:10
  • @Jonasw: `async/await` is part of ES2017 (this years release), not ES7 (ES2016). – Felix Kling Jul 21 '17 at 18:20
  • @felix kling https://m.heise.de/developer/artikel/Features-von-uebermorgen-ES7-Async-Functions-2561894.html , however i trust you more ;) ( or was it renamed ??) – Jonas Wilms Jul 21 '17 at 18:27
  • @Jonasw: Yeah, unfortunately people used to refer to all experimental features as "ES7" before ES7 was released. However, since it is released now, that doesn't make much sense anymore ;) It seems to be preferred to group proposals under ES.next. However as I said, `async/await` was just officially released, so it really is part of ES2017. – Felix Kling Jul 21 '17 at 18:31
1

To call the promises in sequence, you can call the next one in the .then callback. Your conditions (if (!result && ...)) translate pretty easily:

function mainAsync(obj) {
  return (obj.a ? A(obj.a) : Promise.resolve())
    .then(result => !result && obj.b ? B(obj.b) : result)
    .then(result => !result && obj.c ? C(obj.c) : result);
}

If you need to do this for many properties, then you can avoid repeating yourself too much by using a lookup table and a loop (Array#reduce in this case):

const funcs = {
  a: A,
  b: B,
  c: C,
};
const props = ['a', 'b', 'c'];

function mainAsync(obj) {
  return props.reduce(
    (promise, prop) => promise.then(
      result => !result && obj[prop] ? funcs[prop](obj[prop]) : result
    ).catch(() => Promise.resolve(null)),
    Promise.resolve(null)
  );
}
Alex Blex
  • 34,704
  • 7
  • 48
  • 75
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
0
mainAsync = mainSync;

or maybe more easy:

function mainAsync({a,b,c}){
  if(c) return C(c);
  if(b) return B(b);
  if(a) return A(a);
}

If you want all promises to be fullfilled before returning:

function mainAsync({a,b,c}){
 var promises=[];
  if(a) promises.push(A(a));
  if(b) promises.push(B(b));
  if(c) promises.push(C(c));
  return Promise.all(promises).then(val=>val.pop())
}

mainAsync({a:1,b:2,c:3});
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • Looks promising =), but wouldn't Promise. all call all 3 routines, even if A returns result? They are quite expensive, and it is the main reason to call them sequentially starting from the cheapest A to limit number of calls to the most expensive C. – Alex Blex Jul 21 '17 at 18:17
  • @alex blex yes. I was unshure about your usecase. – Jonas Wilms Jul 21 '17 at 18:18
0

If using ES7 transpilers (for async/await) is not an option, you can implement an async control flow using ES2015 generators. Here's a naive example implementation:

function *main() {
  let a = yield Promise.resolve(5);
  let b = yield Promise.resolve(a + 10);
  let c = yield Promise.resolve(b + 15);

  // place the rest of your code here
  console.log(c); // prints 30
}

function execAsync(generator, previousValue) {
  const nextValue = generator.next(previousValue);
  if (!nextValue.done) {
    let promise = nextValue.value;
    // FIXME: promise rejection is not handled
    promise.then(function (value) {
      execAsync(generator, value);
    });
  }
}

// ...    

// start the execution
execAsync(main());
fardjad
  • 20,031
  • 6
  • 53
  • 68