1

I’d just like to run an async function, have it wait till it has a return value, pass that return value to another async function and wash and repeat a few times. I’ve seen some examples but I’m struggling to make them work in test code. Forgive all the baseball context, but I had to have a little fun with this self-inflicted nose bleed.

I can't even make it work without passing return values in the attached code, feeling pretty stupid at the moment...

whosOnFirst(1)
.then(whatsOnSecond(2))
.then(iDunnosOnthird(3)) 

I'd like to see the syntax to have whosOnFirst(1) execute, pass a return value to whatsOnSecond() and pass it's return value to iDunnosOnthird(). Creating the desired output (less the spaces) referenced in the bottom of the snippet.

I really appreciate the help!

// Arbitrary timer with input/output parameters for async understanding;
function waitSec(callerName, pause) { // 
    console.log(`${callerName} called, waiting ${pause} second(s)...`)
    setTimeout(function () {
        console.log(`${callerName} sent ${pause + 1} in a return, and done!`)
        return pause + 1; // delayed return value / adds 1 to input param 
    }, pause * 1000);
}

// Who's On First - run this first with an input param = 1
async function whosOnFirst(i) {
    waitSec("who's on first", 1).then(result => {
        return result;
    })
}

// What's on Second - run after completion of whosOnFirst() using return for param
async function whatsOnSecond(i) {
    waitSec("what's on second", i).then(result => {
        return result;
    })
}

// iDunno's on third - run after completion of whatsOnSecond(i) using return for param
async function iDunnosOnthird(i) {
    waitSec("iDunno's on third", i).then(result => {
        return result;
    })
}

whosOnFirst(1)
    .then(whatsOnSecond(2))
    .then(iDunnosOnthird(3))


// CURRENT OUTPUT: 
// who's on first called, waiting 1 second(s)...
// what's on second called, waiting 2 second(s)...
// iDunno's on third called, waiting 3 second(s)...
// who's on first sent 2 in a return, and done!
// what's on second sent 3 in a return, and done!
// iDunno's on third sent 4 in a return, and done!

// DESIRED OUTPUT:
// who's on first called, waiting 1 second(s)...
// who's on first sent 2 in a return, and done!

// what's on second called, waiting 2 second(s)...
// what's on second sent 3 in a return, and done!

// iDunno's on third called, waiting 3 second(s)...
// iDunno's on third sent 4 in a return, and done!
  • You are executing your callback functions immediately. You need something more like `.then(() => whatsOnSecond(2))` – Phil Mar 04 '20 at 00:04
  • 1
    a) don't use `async` function syntax if you're not using `await` b) `.then(…)` expects a callback *function* - wrap your call in a function expression c) your functions need to `return` the promise chains they're building d) [drop the pointless `.then(result => result)`](https://stackoverflow.com/q/41089122/1048572) e) your `waitSec` function [returns no promise](https://stackoverflow.com/q/22519784/1048572). Have a look at [my rules of thumb](https://stackoverflow.com/a/25756564/1048572) – Bergi Mar 04 '20 at 00:05
  • Also, `waitSec` should return a promise. Eg `return new Promise(res => setTimeout(res, pause * 1000, pause + 1))` – Phil Mar 04 '20 at 00:05
  • Not proper use of comments here, but I just wanted to say "Thank you" to all!. I appreciate everyone's time in attempting to reduce my level of ignorance... – William Stephan Mar 04 '20 at 03:57

4 Answers4

3

Working Solution

Your issue is that the waitSec is in a different context from the rest of the code. You need to lift it to the Promise context.

Then you have "promisified" code all the way down, and you can chain promises. Here it is working (further explanation below it):

// Arbitrary timer with input/output parameters for async understanding;
function waitSec(callerName, pause) { // 
    return new Promise(resolve => {
      console.log(`${callerName} called, waiting ${pause} second(s)...`)
      setTimeout(function () {
        console.log(`${callerName} sent ${pause + 1} in a return, and done!`)
        resolve(pause + 1); // delayed return value / adds 1 to input param 
      }, pause * 1000);
    })
}

// Who's On First - run this first with an input param = 1
   function whosOnFirst(i) {
   return waitSec("who's on first", 1)
}

// What's on Second - run after completion of whosOnFirst() using return for param
function whatsOnSecond(i) {
   return waitSec("what's on second", i)
}

// iDunno's on third - run after completion of whatsOnSecond(i) using return for param
function iDunnosOnthird(i) {
    return waitSec("iDunno's on third", i)
}

whosOnFirst(1)
    .then(whatsOnSecond)
    .then(iDunnosOnthird)
    .then(console.log)

Further Explanation

Your original implementation of waitSec always returns undefined to the caller:

// Arbitrary timer with input/output parameters for async understanding;
function waitSec(callerName, pause) { // 
    console.log(`${callerName} called, waiting ${pause} second(s)...`)
    setTimeout(function () {
        console.log(`${callerName} sent ${pause + 1} in a return, and done!`)
        return pause + 1; // delayed return value / adds 1 to input param 
    }, pause * 1000);
    // return undefined happens here
}

The return inside the setTimeout does not return to your calling code, because your code never calls this function. It is invoked by the JS timer code, asynchronously.

Returning values from async code using a callback

The way to communicate a result out of there to a caller is to either take a callback as a parameter to the outer function and call that callback in the async function, like this:

function waitSec({callerName, pause, callback}) {
      console.log(`${callerName} called, waiting ${pause} second(s)...`)
      setTimeout(function () {
        console.log(`${callerName} sent ${pause + 1} in a return, and done!`)
        callback(pause + 1); // delayed return value / adds 1 to input param 
      }, pause * 1000);
}

In which case you have the classic callback pattern of JS.

Using Promises, and why you might prefer them

Or, you return a Promise, and resolve it to return the value - as I demonstrated in the solution. For more about that, search for "how do I promisify a callback".

The benefit of doing it this way (with a Promise) is that Promises are composable, as demonstrated in the solution.

Promises are also await-able, so you can use async/await with Promise-returning functions.

You do not need to mark the Promise-returning functions as async.

If you mark a function as async, it returns a Promise, like this:

// A function marked async returns a Promise
async function simple() {
  return 3
}

// Proof that it is a Promise 
simple().then(console.log)

// The equivalent code explicitly returning a Promise - this is what `async` does
function simpleP(n = 0) {
  return new Promise(resolve => resolve(n+1))
}

// Proof that it behaves the same
simpleP().then(console.log)

// Promise composition
simple()
  .then(simpleP)
  .then(console.log)
  
// Same functionality, using async/await
async function main() {
  const num = await simple()
  const res = await simpleP(num)
  console.log(`${num} + 1 = ${res}`)
}

main()

Marking synchronous return functions async makes them composable with async code that actually needs a Promise to return a value. You can use it to lift your functions to the same async context and compose them.

// Synchronous function that is Promisified and composable
async function simple() {
   return 3
}

To actually get a value out of an asynchronous function that takes a callback and provides its return via that, you must construct and return a Promise from your function, and then call the Promise resolve inside the callback that provides the value you want to communicate out.

function getDBRecordP(id) {
  return new Promise((resolve, reject) => 
    dbLib.get(id, 
      (err, result) => err ? 
        reject(err) : 
        resolve(result)))
}

In this case, you have to explicitly wire up the Promise machinery. This is pretty much the only time that you need to create and return a Promise.

What it is doing is wrapping a callback interface for you.

function getDBRecordP(id) {
  return new Promise((resolve, reject) => 
    dbLib.get(id, 
      (err, result) => err ? 
        reject(err) : 
        resolve(result)))
}

Notice in the callback example, you had to take a callback argument from the caller as a parameter, and then call that with the return value inside the callback that provides the value.

With the Promise machinery, you do take a callback argument, but it is not provided by the caller, it is provided by the Promise that you construct. And then you return the Promise to the caller.

The getDBRecordP function above is the essence of "how do I convert a function that takes a callback into a function that returns a Promise?".

The caller then passes in the callback to the the Promise.then() method.

So you have a machine that wraps the callback passing convention into a formal API that is composable, which callbacks are not.

The other aspect of the Promise machine is the .catch() method.

Whereas with callbacks, the convention has always been that a callback signature is (err, result), the Promise machine allows you to provide two callbacks - one for err and one for result.

Josh Wulf
  • 4,727
  • 2
  • 20
  • 34
  • 2
    Hi Josh! That's a well above and beyond post IMHO. Thanks you for all the examples, I'll be using this as reference as it will be a while before I visualize the program flow in this way. – William Stephan Mar 04 '20 at 04:11
1

Please utilize await keyword that is beautiful for async function. Your waitSec function should return a promise, also when you are resolving a promise, your value comes in .then((res)=> console.log(res)), use it for chaining like below:

// Arbitrary timer with input/output parameters for async understanding;
function waitSec(callerName, pause) { // 

  return new Promise((resolve, reject) => {
    console.log(`${callerName} called, waiting ${pause} second(s)...`)
    setTimeout(function() {
      console.log(`${callerName} sent ${pause + 1} in a return, and done!`)
      resolve(pause + 1); // delayed return value / adds 1 to input param 
    }, pause * 1000);
  })

}

// Who's On First - run this first with an input param = 1
async function whosOnFirst(i) {
  const resp = await waitSec("who's on first", 1);
  return resp;
}

// What's on Second - run after completion of whosOnFirst() using return for param
async function whatsOnSecond(i) {
  const resp = await waitSec("what's on second", i);
  return resp;
}

// iDunno's on third - run after completion of whatsOnSecond(i) using return for param
async function iDunnosOnthird(i) {
  const resp = await waitSec("iDunno's on third", i);
  return resp;
}


whosOnFirst(1).then((firstResp) => {
  whatsOnSecond(firstResp).then((secResp) => {
    iDunnosOnthird(secResp).then((thirdResp) => {
      console.log(thirdResp);
    })
  })
})

You can also use the values in chain like below with async/await:

// Arbitrary timer with input/output parameters for async understanding;
function waitSec(callerName, pause) { // 

  return new Promise((resolve, reject) => {
    console.log(`${callerName} called, waiting ${pause} second(s)...`)
    setTimeout(function() {
      console.log(`${callerName} sent ${pause + 1} in a return, and done!`)
      resolve(pause + 1); // delayed return value / adds 1 to input param 
    }, pause * 1000);
  })

}

// Who's On First - run this first with an input param = 1
async function whosOnFirst(i) {
  const resp = await waitSec("who's on first", 1);
  return resp;
}

// What's on Second - run after completion of whosOnFirst() using return for param
async function whatsOnSecond(i) {
  const resp = await waitSec("what's on second", i);
  return resp;
}

// iDunno's on third - run after completion of whatsOnSecond(i) using return for param
async function iDunnosOnthird(i) {
  const resp = await waitSec("iDunno's on third", i);
  return resp;
}



let test = async() => {
  var res1 = await whosOnFirst(1);
  var res2 = await whatsOnSecond(res1);
  var res3 = await iDunnosOnthird(res2);
  console.log(res3);
}

test();
Sunil Lama
  • 4,531
  • 1
  • 18
  • 46
0

You need to await for each result like that:

async function main() {
  const result1 = await whosOnFirst(1);
  // whosOnSecond will not get called except after the whosOnFirst is done.
  const result2 = await whosOnSecond(result1);
}
Dharman
  • 30,962
  • 25
  • 85
  • 135
mosmk
  • 403
  • 2
  • 7
  • I rechecked my answer and I do not think it is that bad, can someone tell me why the down-vote? – mosmk Mar 04 '20 at 00:17
  • 1
    i don't know why people downvoted you, but your chaining is infact the preferred one as well, upvoted you. – Sunil Lama Mar 04 '20 at 00:41
0

Here's how it's supposed to work:

function delay(milliseconds){
  return new Promise((resolve)=>{
    setTimeout(()=>{
      resolve();
    }, milliseconds);
  });
}
async function test(){
  console.log('no delay, yet');
  await delay(1000);
  console.log('After 1 second');
  await delay(1000);
  console.log('2 seconds have passed');
  await delay(3000);
  console.log('Oh look, 5 seconds have passed!');
}
test();

async functions await until a Promise is resolved, then go to the next block of code after the Promise, possibly awaiting again... and again. I admit I thought it was strange that an async function really runs the awaits synchronously, but the async function itself it asynchronous... meaning another function executed immediately after an executed async function is likely to return results before the async function executes.

In regards to your example:

class Counter{
  constructor(increment = 1, initially = 0){
    this.increment = increment; this.count = initially;
  }
  inc(){
    return this.count += this.increment;
  }
  dec(){
    return this.count -= this.increment;
  }
}
function delay(milliseconds){
  return new Promise((resolve)=>{
    setTimeout(()=>{
      resolve();
    }, milliseconds);
  });
}
const sec = new Counter;
function logCaller(callerName){
  console.log(`${callerName} called, after ${sec.inc()} second(s)...`);
}
async function what(){
  await delay(1000);
  logCaller("who's on first");
  await delay(1000);
  logCaller("what's on second");
  await delay(1000);
  logCaller("iDunno's on third");
}
what();
StackSlave
  • 10,613
  • 2
  • 18
  • 35