简体   繁体   中英

ECMAScript 2015 call Promises sequentially

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.

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) })(); 

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)
  );
}
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});

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());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM