19

UPDATE

I have read over a dozen articles on this topic and not one of them addresses this fundamental question. I am going to start listing a resources section at the end of this post.

ORIGINAL POST

My understanding of an async function is it returns a promise.

MDN docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

Inside my program I could write something like:

function testPromise() {
  return new Promise((resolve, reject) => {
    // DO WORK
    reject() // IF WORK FAILS
    resolve() // IF WORK IS SUCCESSFUL        
  })
}

async function mainFunction() {
  let variable
  try {
    variable = await testPromise()
  } catch(e) {
    throw e
  }
  return variable
}

I could also write testPromise as an async function and await that in the same context.

async function testAsyncFunction() {
  //DO WORK AND THROW ERROR IF THEY OCCUR      
}

async function mainFunction() {
  let variable
  try {
    variable = await testAsyncFunction()
  } catch(e) {
    throw e
  }
  return variable
}

Which would be considered best practice? If I wish to create asynchronous operation, should the function use return New Promise and awaited in a async function or is awaiting an async function from an async function the same difference?

RESOURCES

JavaScript ES 2017: Learn Async/Await by Example https://codeburst.io/javascript-es-2017-learn-async-await-by-example-48acc58bad65

Javascript — ES8 Introducing async/await Functions https://medium.com/@reasoncode/javascript-es8-introducing-async-await-functions-7a471ec7de8a

6 Reasons Why JavaScript’s Async/Await Blows Promises Away (Tutorial) https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9

----------------------CURRENT----------------------

export default function time_neo4jUpdate({ store, action, change, args, }) {
  return new Promise(async (resolve, reject) => {
    try {
      const {
        thing: { type },
        nonValidatedArgs: { leapYear, root, start, end },
          _neo4j,
          _cypherReducers,
          _neo4jCreateReduce,
          _timetreeSteps: { update }
      } = store.getState()
      let results = []
      for (let i = 0; i < _neo4jCreateReduce.length; i++) {
        const result = await _neo4j.session(
          _neo4jCreateReduce[i],
          _cypherReducers.runQuery(update, i, root, start, end))
        results = [...results, result]
      }
      resolve({
        store,
        action: 'NEO4J_UPDATE',
        change: results,
        args
      })
    } catch (e) {
      const m = `NEO4J TIMETREE UPDATE: Unable to complete the UPDATE step for the timetree. WHAT: ${e}`
      reject(m)
    }
  })
}

----------------------AS ASYNC FUNCTION----------------------

export default async function time_neo4jUpdate({ store, action, change, args, }) {
  try {
    const {
      thing: { type },
      nonValidatedArgs: { leapYear, root, start, end },
      _neo4j,
      _cypherReducers,
      _neo4jCreateReduce,
      _timetreeSteps: { update }
    } = store.getState()
    let results = []
    for (let i = 0; i < _neo4jCreateReduce.length; i++) {
      const result = await _neo4j.session(
        _neo4jCreateReduce[i],
        _cypherReducers.runQuery(update, i, root, start, end))
      results = [...results, result]
    }
    return {
      store,
      action: 'NEO4J_UPDATE',
      change: results,
      args
    }
  } catch (e) {
    const m = `NEO4J TIMETREE UPDATE: Unable to complete the UPDATE step for the timetree. WHAT: ${e}`
    throw m
  }
}
Cazineer
  • 2,235
  • 4
  • 26
  • 44
  • Can you give us an example of the "work" code you're talking about? Asynchronous code would almost always either be based upon existing promises or use promises to convert callback-based interfaces to promises. – JLRishe Nov 01 '17 at 04:18
  • The function I posted is awaited in another async function. I'd like it to simply be an async function and not have to `return new Promise` – Cazineer Nov 01 '17 at 04:23
  • Thank you. Updated my answer. – JLRishe Nov 01 '17 at 04:36
  • Are you intentionally running the `_neo4j.session` queries in sequence, or would you prefer to run them in parallel? – JLRishe Nov 01 '17 at 05:44
  • They must run in sequence. `_neo4j.session` is in fact not the actual driver but a wrapper around the driver. it allows us to pass in dynamically generated Cypher queries and objects to UNWIND. These are created by passing in a Redux style action to a to a reducer function that returns the correct Cypher string and a new generated array of objects to UNWIND. The program itself was designed using a model similar to Redux as we use a polyglot persistence. When an action takes place like create, we create an immutable state object that can be updated along the way and if any database fails... – Cazineer Nov 01 '17 at 05:52
  • We can simply rollback the entire thing. Think like a Redux state that gets updated and recorded as data is written to Neo4j, MongoDB, PostgreSQL and Redis and if any fail, a method is called on the state object and everything is undone. – Cazineer Nov 01 '17 at 05:53

1 Answers1

7

Even without the availability of async/await, you should very rarely need to use new Promise(). If you're using it a lot, it's typically a code smell.

The whole point of async/await is that it allows you to avoid a lot of the situations where you would otherwise need to work with promises explicitly.

So if it's supported in the environment you're targeting (Internet Explorer does not support async/await) or you're using a transpiler, go ahead and use it anywhere you can. That's what it's for.

Bear in mind that this is pointless:

catch(e) {
    throw e;
}

There's no point in catching an error just to rethrow it. So if you're not actually doing anything with the caught error, don't catch it:

async function testAsyncFunction() {
  //DO WORK AND THROW ERROR IF THEY OCCUR
  return value
}

Edit: Now that you've provided an example of your code, I can answer with more certainty: If your function is based upon existing promises, then by all means, it is good to use async/await and you usually should not use new Promise():

export default async function time_neo4jUpdate({
    store,
    action,
    change,
    args,
  }) {
    try {
      const {
        thing: {
          type
        },
        nonValidatedArgs: {
          leapYear,
          root,
          start,
          end
          },
          _neo4j,
          _cypherReducers,
          _neo4jCreateReduce,
          _timetreeSteps: {
            update
        }
      } = store.getState()

      const results = await _neo4jCreateReduce.reduce(async function (acc, el) {
        const result = await _neo4j.session(
          el,
          _cypherReducers.runQuery(
            update,
            i,
            root,
            start,
            end
          )
        )

        return [...(await acc), result]
      }, []);

      return {
        store,
        action: 'NEO4J_UPDATE',
        change: results,
        args
      };
    } catch (e) {
      const m = `NEO4J TIMETREE UPDATE: Unable to complete the UPDATE step for the timetree. WHAT: ${e}`
      throw m;
    }
}
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • @Kainan Ok, thank you. My advice remains the same: if using an `async` function is an option, then go ahead and do so. That's what they're for. – JLRishe Nov 01 '17 at 04:17
  • I was using the spread operator so that I never mutate the original array, always returning a new array. My personal reference is a program that never mutates any data. :) Maybe that was going to far lol – Cazineer Nov 01 '17 at 06:00
  • @Kainan If you're repeatedly overwriting the `results` variable, you're already mutating data. `[...results, result]` is just extra noise at that point. You're also mutating the `i` variable. But you could avoid mutability by changing that `for` to a `.reduce()`. – JLRishe Nov 01 '17 at 06:47
  • @JLRische I believe you are fundamentally wrong here. `[...results, result]` is creating a new array and spreads in the contents of the previous array. This is how Redux's reducers work. It's no different than `Object.assign({}, value)` being written as `{...{}, value}`. In fact, I am not overwriting anything. The results variable is a reference to the object in memory. When I create a new array, I am changing the reference to the new array (object). Reassigning. The previous object remains in memory until the function returns. i is also not being mutated. i is a primitive and there is no... – Cazineer Nov 01 '17 at 15:58
  • no mutating primitives as they contain no references. `[...results, result]` didn't create a new array, Redux as a whole wouldn't work. I've tried working with reduce and await together and have run into problems in the past. I may have been doing something wrong and will attempt again. Thanks for the feedback. – Cazineer Nov 01 '17 at 16:01
  • @Kainan I didn't say that you were mutating an array. I said that you were repeatedly overwriting a variable and I don't see that as any better than avoiding the mutation of an array that will never be used anywhere outside of this function. There's a reason why most functional languages treat variables like JavaScript `const` by default. It is definitely possible to use `.reduce()` with `await` and I modified my code to show how that can be done. Here is a simpler example: http://jsfiddle.net/72rL6Lqb/1/ – JLRishe Nov 01 '17 at 17:58
  • I just got the reduce version working. I did some performance testing and it takes 50 seconds using the reduce version and 33 second using the for loop on 550k nodes in Neo4j. The reduce version was 40% slower. Regardless, it was a good exercise and you've shown us how to await inside reduce. Thanks. Cheers – Cazineer Nov 01 '17 at 18:05