1

I'm trying deal with a library that using async functions and am a little lost. I want to call a function that returns a string but am getting tripped up. Here's what I have so far. The ZeroEx library functions all seem to use async /await so my understanding is that I can only call them from another async method. But won't this just cause a chain reaction meaning every method needs to be async? Or am I missing something?

function main() {
    var broker = zmq.socket('router');
    broker.bindSync('tcp://*:5671');


    broker.on('message', function () {
    var args = Array.apply(null, arguments)
        , identity = args[0]
        , message = args[1].toString('utf8');

        if(message === 'TopOfBook') {
            broker.send([identity, '', getTopOfBook()]);
        }

        //broker.send([identity, '', 'TEST']);
        //console.log('test sent');
    })
}

async function getTopOfBook() {
    var result: string = 'test getTopOfBook';
    const EXCHANGE_ADDRESS = await zeroEx.exchange.getContractAddress();
    const wethTokenInfo = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync('WETH');
    const zrxTokenInfo = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync('ZRX');

    if (wethTokenInfo === undefined || zrxTokenInfo === undefined) {
        throw new Error('could not find token info');
    }

    const WETH_ADDRESS = wethTokenInfo.address;
    const ZRX_ADDRESS = zrxTokenInfo.address;


    return result;
}

main();

The function getTopOfBook() isn't returning anything back to the main() function so the result is never being sent by the broker. The commented out broker.send() with 'TEST' is working fine however. Thanks for looking.

EDIT: I tried to make the main method async so I could use await but it's giving an error that I can only use await in an async function. Could the broker.on() call be causing this?

  const main = async () => {
      try{
      var broker = zmq.socket('router');
      broker.bindSync('tcp://*:5671');


      broker.on('message', function () {
      var args = Array.apply(null, arguments)
          , identity = args[0]
          , message = args[1].toString('utf8');

          console.log(message);
          if(message === 'TopOfBook') {
>>            var test = await getTopOfBook();
              console.log('in top of book test');

              broker.send([identity, '', test]);
          }

          //broker.send([identity, '', 'TEST']);
          //console.log('test sent');
      })
      } catch (err) {
          console.log(err);
      }
  }

EDIT 2: My current working code, thanks everyone that had advice/solutions! I obviously have to fill out the getTopOfBook() function to return an actual result still. If you have more recommendations send them my way. I'm trying to build out a backend that will get data from a geth rpc and send it to a C# GUI front end.

var main = function() {
    try{
    var broker = zmq.socket('router');
    broker.bindSync('tcp://*:5672');

    broker.on('message', function () {
    var args = Array.apply(null, arguments)
        , identity = args[0]
        , message = args[1].toString('utf8');

        if(message === 'TopOfBook') {
            getTopOfBook().then((result) => {
                broker.send([identity, '', result])
            });
        }
    })
    } catch (err) {
        console.log(err);
    }
}

async function getTopOfBook() {
    var result: string = 'test getTopOfBook';
    const EXCHANGE_ADDRESS = await zeroEx.exchange.getContractAddress();
    const wethTokenInfo = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync('WETH');
    const zrxTokenInfo = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync('ZRX');

    if (wethTokenInfo === undefined || zrxTokenInfo === undefined) {
        throw new Error('could not find token info');
    }

    const WETH_ADDRESS = wethTokenInfo.address;
    const ZRX_ADDRESS = zrxTokenInfo.address;


    return result;
}

main();
jawknee530
  • 319
  • 4
  • 12
  • 1
    "*won't this just cause a chain reaction meaning every method needs to be async?*" - yes. If any part of a functionality is asynchronous, it cannot immediately deliver the result. – Bergi Feb 05 '18 at 17:01
  • "*The ZeroEx library functions all seem to use async /await*" all that means is that they return Promise objects. – Paul Feb 05 '18 at 17:01
  • 1
    `getTopOfBook` also returns a Promise so you either need to use `await` with it and make `main` async, or do this: `getTopOfBook().then( result => broker.send([identity, '', result]) );` – Paul Feb 05 '18 at 17:03
  • Does the broker need to respond immediately? If no, then just wait for the `getTopOfBook()` promise before sending the response with the result. – Bergi Feb 05 '18 at 17:03
  • @Bergi it would be preferable to have immediate returns but not necessary. Also, there's no way for synchronous and async code to coexists in a case like this? – jawknee530 Feb 05 '18 at 17:07
  • @Paulpro is there a specific way to handle a promise object beyond just calling await on it? And can you explain that line of code a bit? Sorry for the confusion and thanks, very new in the javascript world. – jawknee530 Feb 05 '18 at 17:07
  • @jawknee530 That line is another way to run code after a Promise has resolved without using `await`. The callback function to [then](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) runs asynchronously after the Promise returned by `getTopOfBook` has resolved. – Paul Feb 05 '18 at 17:10
  • @Paulpro Thanks, got it. I tried changing the function main to an async and using await but I got an error complaining that await could only be called inside of an async method. would the .on() call from broker be causing that error? – jawknee530 Feb 05 '18 at 17:12
  • 1
    Sorry, I didn't even notice that code is running in another anonymous function. It's that function that you would need to make async, not main, EG. `broker.on('message', async function () {` – Paul Feb 05 '18 at 17:14
  • @jawknee530 Then you can use await like this: `broker.send([identity, '', await getTopOfBook()]);` or the way you have it in your edit. – Paul Feb 05 '18 at 17:14
  • @jawknee530 They can ([and should](https://stackoverflow.com/a/45448272/1048572)) coexist, but it is not possible for a synchronous method to use an asynchronous method by definition. – Bergi Feb 05 '18 at 17:15
  • Thanks guys (gals?) I'll give your recommendations some tries and see what I can shake out. I might (probably will) be back though with more questions! – jawknee530 Feb 05 '18 at 17:17

3 Answers3

0

You are missing the point that async functions are just a way to write the code. Every async function what actually does is generate a promise. That is why you can await any promise and you can interact with any library that uses async without the need of using async functions yourself.

When you call an async function it should return a promise (which happens automatically on async function). A promise is nothing but an object with a then method. Such method accepts a callback, where you can handle the rest of the logic.

function main() {
    var broker = zmq.socket('router');
    broker.bindSync('tcp://*:5671');


    broker.on('message', function () {
    var args = Array.apply(null, arguments)
        , identity = args[0]
        , message = args[1].toString('utf8');

        if(message === 'TopOfBook') {
            return getTopOfBook().then( result => 
                broker.send([identity, '', result])
            ) // If the broker also returns a promise, you can continue the flow here
            .then(()=> console.log('test sent'))
        }
    })
}

Personally I don't like async await at all because the involve too much magic and make people to forget about the actual nature of promises and asynchronous code.

Also when dealing with promises you should always remember to return any promise you could call/generate so outer code can continue the chain and handle error messages.

Danielo515
  • 5,996
  • 4
  • 32
  • 66
  • thanks. Do I have to do anything in the getTopOfBook() function in order to have it generate/return a promise or is that handled automatically by declaring the function as async? – jawknee530 Feb 05 '18 at 17:36
  • @jawknee530 **Async function doc:** *When an async function is called, it returns a Promise*, so you don't need to do anything :-) – Ele Feb 05 '18 at 17:46
  • As @Ele said, you don't need to do anything extra, is automatic – Danielo515 Feb 05 '18 at 17:50
0

Your function getTopOfBook returns a Promise, so you need to use the function then

Call that function as follow:

getTopOfBook().then((result) => {
  console.log("Result:" + result);
});

Look at this code snippet

let sleep = (fn) => {
  setTimeout(fn, 1000);
};

let getContractAddress = function(cb) {
  return new Promise((r) => sleep(() => {
    r('getContractAddress')
  }));
};

let getTokenBySymbolIfExistsAsync = function(str) {
  return new Promise((r) => sleep(() => {
    r({
      address: 'getTokenBySymbolIfExistsAsync: ' + str
    })
  }));
};

let WETH_ADDRESS = '';
let ZRX_ADDRESS = '';
let EXCHANGE_ADDRESS = '';

async function getTopOfBook() {
  var result = 'test getTopOfBook';

  const EXCHANGE_ADDRESS = await getContractAddress();
  const wethTokenInfo = await getTokenBySymbolIfExistsAsync('WETH');
  const zrxTokenInfo = await getTokenBySymbolIfExistsAsync('ZRX');

  if (wethTokenInfo === undefined || zrxTokenInfo === undefined) {
    return Promise.reject(new Error('could not find token info'));
  }

  const WETH_ADDRESS = wethTokenInfo.address;
  const ZRX_ADDRESS = zrxTokenInfo.address;

  console.log(WETH_ADDRESS);
  console.log(ZRX_ADDRESS);
  console.log(EXCHANGE_ADDRESS);

  return result;
}

var main = function() {
  console.log('Waiting response...');
  getTopOfBook().then((result) => {
    console.log("Result:" + result);
    console.log('DONE!');
  }).catch((error) => {
      console.log(error);
  });      
};

main();
.as-console-wrapper {
  max-height: 100% !important
}

If you want to throw an error use the function Promise.reject()

  • Along with that call, you need either to pass the reject function or call the catch function.

In this example, we're passing the reject function:

(error) => {
    console.log(error); 
}

If you don't pass the reject function, you need to call the catch function in order to handle the thrown error.

let sleep = (fn) => {
  setTimeout(fn, 1000);
};

let getContractAddress = function(cb) {
  return new Promise((r) => sleep(() => {
    r('getContractAddress')
  }));
};

let getTokenBySymbolIfExistsAsync = function(str) {
  return new Promise((r) => sleep(() => {
    r()
  }));
};

let WETH_ADDRESS = '';
let ZRX_ADDRESS = '';
let EXCHANGE_ADDRESS = '';

async function getTopOfBook() {
  var result = 'test getTopOfBook';

  const EXCHANGE_ADDRESS = await getContractAddress();
  const wethTokenInfo = await getTokenBySymbolIfExistsAsync('WETH');
  const zrxTokenInfo = await getTokenBySymbolIfExistsAsync('ZRX');

  if (wethTokenInfo === undefined || zrxTokenInfo === undefined) {
    return Promise.reject('Could not find token info');
  }

  const WETH_ADDRESS = wethTokenInfo.address;
  const ZRX_ADDRESS = zrxTokenInfo.address;

  console.log(WETH_ADDRESS);
  console.log(ZRX_ADDRESS);
  console.log(EXCHANGE_ADDRESS);

  return result;
}

var main = function() {
  console.log('Waiting response...');
  getTopOfBook().then((result) => {
    console.log("Result:" + result);
    console.log('DONE!');
  }, (error) => {
    console.log(error); 
  }).catch((error) => {
    console.log(error); // This line will be called if reject function is missing.
  });

};

main();
.as-console-wrapper {
  max-height: 100% !important
}

Resource

When an async function is called, it returns a Promise. When the async function returns a value, the Promise will be resolved with the returned value. When the async function throws an exception or some value, the Promise will be rejected with the thrown value.

Ele
  • 33,468
  • 7
  • 37
  • 75
  • `async-await` model is used, so there is no need for `then` – Akash Dathan Feb 05 '18 at 17:42
  • @AkashDathan how do you get the result of `return result;` within function `getTopOfBook`?. async functions return `Promise` objects, so you need to call `then` function. – Ele Feb 05 '18 at 17:45
  • Yes, `then` can be used. But what he has used is `await getTopOfBook()`, when `await` is used the resolved promise value is returned. – Akash Dathan Feb 05 '18 at 17:48
  • @AkashDathan sorry, you're totally right!! I was talking about this answer. – Ele Feb 05 '18 at 17:48
  • @jawknee530 please update your question with the new version for checking better. – Ele Feb 05 '18 at 17:58
  • @Ele, done. how would I handle the promise rejection in this case? – jawknee530 Feb 05 '18 at 18:36
0

The callback function needs to be async

broker.on('message', async function () {
    var args = Array.apply(null, arguments)
        , identity = args[0]
        , message = args[1].toString('utf8');

        if(message === 'TopOfBook') {
            var test = await getTopOfBook();
            broker.send([identity, '', test]);
        }

        //broker.send([identity, '', 'TEST']);
        //console.log('test sent');
    })
Akash Dathan
  • 4,348
  • 2
  • 24
  • 45