Make sure call "Function" is finished running, before doing next step (Meteor Method)?

#1

I have the problem with call Function in For Loop.
The loop run to next, before function is finished???
Ex:

function myFun(){
  // Collection.insert, update, remove...
  // Do something else

  return 'success'
}
---
Meteor.methods({
  removeData(params) // params = [1,2,3...]
    params.forEach(id => {
      Collection.remove(id, error => {
         // Call function
         myFun(.....) // Make sure function is finished, before next loop
      })
    })

    // Doing somethisng
    return '......'
  })

Please help me

#2

Can you assign the function to a variable like so inside the loop:

const variable = myFun(.....)

#3

thanks for your reply, I will try.
But I don’t want to use this variable.

#4

Don’t use the callback in remove. Then it will work as you want it to.

Meteor.methods({
  removeData(params) // params = [1,2,3...]
    params.forEach(id => {
    try {
      Collection.remove(id);
      myFun(.....) // Make sure function is finished, before next loop
    } catch (error) {
      throw new Meteor.Error(error.message);
    })
    // Doing somethisng
    return '......'
  })

I should also add, if you’re doing the same thing (using callbacks) in myFun, you’ll need to remove those callbacks, too.

1 Like
#5

Very thanks @robfallows :sunny:

1 Like
#6

@robfallows, Excuse me could I use Async/Await for this?

Meteor.methods({
  async removeData(params) // params = [1,2,3...]
    params.forEach(id => {
      await Collection.remove(id, error => {
         // Call function
         myFun(.....) // Make sure function is finished, before next loop
      })
    })
    // Doing somethisng

    return '......'  
})
#7

You can use async/await - but not like that. Maybe more like this:

Meteor.methods({
  async removeData(params) // params = [1,2,3...]
    params.forEach(async (id) => {
      await Collection.rawCollection.remove({_id: id})
      // Call function
      myFun(.....) // Make sure function is finished, before next loop
    })
    // Doing somethisng

    return '......'  
})

If myFun is also an async function, you will need to await that as well.

#8

Thanks for your reply, I am not clear about this.
Why/When use double async

async removeData(params) { // params = [1,2,3...]
    params.forEach(async (id) => {
#9

Understand the fundamentals: https://medium.com/front-end-hacking/callbacks-promises-and-async-await-ad4756e01d90

1 Like
#10

To answer that, you need to consider what you are writing.

Whenever you need to await a function, the enclosing function body must be declared as async:

function outer1() {
  await someFunction(); // will error, because outer1 must be async
}

async function outer2() {
  await someFunction(); // will work, because outer2 is async
}

In your code, you have something which looks a bit like this:

//Meteor.methods({
  function removeData(params) {
    params.forEach(
      async function(id) {
        await someFunction(); // This is await, so ^^this^^ must be async
        myFun(.....); 
      }
    );
  // Do something
  return '......';
//})

Notes:

  1. You do not need an async removeData unless you use await where you have “Do something”.
  2. If myFun is async, then you must await it.
  3. That example is not really as simple as I have indicated. I don’t know the rest of your code, and you may need additional steps.
  4. Unless you must use async/await (and it’s often unnecessary in Meteor), you must make sure you understand it, as @diaconutheodor says :slight_smile:.
1 Like
#11

Thanks for all :blush:

#12

@robfallows @diaconutheodor, Now I have problem with my code of Aggregate (multi level of async/await)

Meteor.methods({
  async myMethod() 
    let data = await Collection.rawCollection. aggregate([...]).toArray()
    data.forEach(async (obj) => {
       let var1 = await Collection1.rawCollection. aggregate([...]).toArray()
       let var2 = await Collection2.rawCollection. aggregate([...]).toArray()
       let var3 = await Collection3.rawCollection. aggregate([...]).toArray()

       obj.var1 = var1[0] // can't get
       obj.var2 = var2[0] // can't get
       obj.var3 = var3[0] // can't get
    })
    .......
    console.log(data) // don't get at all

    return .... 
})

Please help me

#13

Without looking too closely at your code, the obvious thing is that you should be using rawCollection(), not rawCollection.

#14

Thanks for your reply, correct code here

Meteor.methods({
  async myMethod() 
    let data = await Collection.rawCollection().aggregate([...]).toArray()
    data.forEach(async (obj) => {
       let var1 = await Collection1.rawCollection().aggregate([...]).toArray()
       let var2 = await Collection2.rawCollection().aggregate([...]).toArray()
       let var3 = await Collection3.rawCollection().aggregate([...]).toArray()

       obj.var1 = var1[0] // can't get
       obj.var2 = var2[0] // can't get
       obj.var3 = var3[0] // can't get
    })
    .......
    console.log(data) // don't get at all

    return .... 
})

I can’t get var1, var2, var3 at all

#15

I can’t see why that won’t work, unless you’re doing something in code you’ve not shown.

Equally, I can’t see why you can’t get these. However, I also don’t see why you’re setting properties of an intermediate object, which is never used beyond that point.

Is there more code you’re not showing, which may explain the behaviour?

#16

It don’t wait to assign the results before method return.

#17

Other example here

    [1, 2, 3].forEach(async (val) => {
       await delayFun()
       console.log(val)
    })
    .......
    console.log('done')
---------
done
1
2
3
#18

And now I found

  • Process array in sequence
    To wait the result we should return back to old-school “for loop”, but this time we can use modern version with for…of construction (thanks to Iteration Protocol) for better readability:
    image
  • Process array in parallel
    We can slightly change the code to run async operations in parallel:
    image

But don’t understand

#19

The issue is here:

data.forEach(async item => {

async functions return promises immediately when invoked, so the forEach completes and moves on to the console.log line.

The async work delegated to the async functions has not yet completed at this point and so the data is not yet modified.

Since it seems you are already in an async function (since you’ve used await higher in the code), you can wait for all the promises to resolve with await and Promise.all

Promise.all expects an array of promises, so instead of forEach we can use map to create an array of promises

await Promise.all( data.map(aysnc item => { ...

Like forEach, map will iterate over all of the items and run the given function. Unlike forEach, map will return an array with the results of these functions, and given they are async functions, each one will return a promise.

Now we use Promise.all to create a single promise that will resolve when every one of the async functions have finished. And we use await to tell the function to pause until that new promise resolves.

Pausing the function with await means the console.log won’t run until every one of those async functions have finished, meaning it will have the correct data when it does run.

2 Likes
#20

Thanks for your explain