5

I'm using react-native firebase to save my media into firebase. I have a set of URLs that need to be saved in the firebase. For that, I'm using map() to save one by one. After saving I'm pushing the success URL to the array. I need to call a function with this array as a parameter. So I need to call this function after the array is completed. I have searched a lot but I did not find a good explanation for this task. Can anyone help me. Thank you.

var mediaArray = []; //array of success callbacks values
var completedMediaSurveysAnswers = [{}, {}, {}]; //object array of URLs and media types
completedMediaSurveysAnswers.map((i) => {
  try {
    const storage = firebase.storage();
    const mRef = storage.ref('portal').child('Survey/Image/user/' + uuidv4() + 'media');
    mRef.putFile(i.urlPath, {
        contentType: i.mediaType
      })
      .on('state_changed', snapshot => {},
        err => {
          console.log('Failed to upload file to firebase storage')
        },
        uploadedFile => {
          // Success
          this.setState({
            mediaPath: uploadedFile.downloadURL
          })
          mediaArray.push(this.state.mediaPath)
        });
  } catch (error) {
    console.log(error)
  }
})

//need to call this function after loop is done
saveAnswers(mediaArray)
VLAZ
  • 26,331
  • 9
  • 49
  • 67
sd_dewasurendra
  • 383
  • 6
  • 22
  • Do not use `.map` for simple iteration. Use `.forEach` for that. Mapping is a process that transforms each entry into another, so you get 1:1 copy of an array with all items transformed by the mapping operation. – VLAZ Oct 17 '19 at 05:21
  • as above ... unless the point of doing a .map is to return an array of Promises that you can *wait* for – Bravo Oct 17 '19 at 05:23
  • While the comment from @VLAZ is correct, `.map()` would be a valid option if used correctly. So instead of pushing elements by hand into `mediaArray` it should be `mediaArray = completedMediaSurveysAnswers.map(...)` (plus some adjustments in the callback). But this won't help in this case -> [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Andreas Oct 17 '19 at 05:25
  • @VLAZ thank you. I will use forEach. But I'm confused with how to use await/async or promises in this case :( – sd_dewasurendra Oct 17 '19 at 05:26
  • well, step one, you need some Promises to await ... do you have any in your code? no you don't - so, you'll need to make some :D – Bravo Oct 17 '19 at 05:31

4 Answers4

3

You can use Promise or async/await to handle all kind of these situations like this:

var mediaArray = [];
var completedMediaSurveysAnswers = [{}, {}, {}];

async function handleYourTask() {
  await completedMediaSurveysAnswers.map((i) => {
  try {
    const storage = firebase.storage();
    const mRef = storage.ref('portal').child('Survey/Image/user/' + uuidv4() + 'media');
    mRef.putFile(i.urlPath, {
        contentType: i.mediaType
      })
      .on('state_changed', snapshot => {},
        err => {
          console.log('Failed to upload file to firebase storage')
        },
        uploadedFile => {
          // Success
          this.setState({
            mediaPath: uploadedFile.downloadURL
          })
          mediaArray.push(this.state.mediaPath)
        });
    } catch (error) {
      console.log(error)
    }
  })

  await saveAnswers(mediaArray);
}

and then you can call handleYourTask function anywhere you want :)

Hamed Navabian
  • 720
  • 7
  • 11
1

using .map is good, so you can return an Array of promises which you can then wait for their resolution

in this case, the resolved value in Promise.all will be what you were pushing into an array ... i.e. this.state.mediaPath

var completedMediaSurveysAnswers = [{}, {}, {}]; //object array of URLs and media types
var promises = completedMediaSurveysAnswers.map((i) => new Promise((resolve, reject) => {
    try {
        const storage = firebase.storage();
        const mRef = storage.ref('portal').child('Survey/Image/user/' + uuidv4() + 'media');
        mRef.putFile(i.urlPath, {
            contentType: i.mediaType
        }).on('state_changed', snapshot => {}, err => {
            console.log('Failed to upload file to firebase storage');
            resolve(null); // so one failure doesn't stop the whole process
        }, uploadedFile => {
            // Success
            this.setState({
                mediaPath: uploadedFile.downloadURL
            })
            resolve(this.state.mediaPath)
        });
    } catch (error) {
        console.log(error)
        resolve(null); // so one failure doesn't stop the whole process
    }
}));

//need to call this function after loop is done
Promise.all(promises).then(mediaArray => {
    saveAnswers(mediaArray);
});
Bravo
  • 6,022
  • 1
  • 10
  • 15
  • In each iteration this.state.mediaPath should push into the mediaArray right? – sd_dewasurendra Oct 17 '19 at 05:49
  • incorrect ... the promise is resolved ... then `mediaArray` is an array result of the promises - see how `mediaArray` is **not** declared anywhere as a variable, it's an argument to the .then callback – Bravo Oct 17 '19 at 05:55
  • So that means from **resolve(this.state.mediaPath)** line it will add one by one all the promises right? – sd_dewasurendra Oct 17 '19 at 06:33
  • indeed, and the array result will be the same order as `completedMediaSurveysAnswers` - unlike in your original code where that was not guaranteed – Bravo Oct 17 '19 at 07:19
  • Now I get it :) thank you for your answer. Cheers!! – sd_dewasurendra Oct 17 '19 at 10:40
1

As per my knowledge we can dom something like below.

Learn From : https://www.codegrepper.com/code-examples/javascript/wait+for+map+to+finish+javascript

Perform an action after .map() completion:

var promises = testUserArray.map((item, index) => {
    console.log('item', item)
})

await Promise.all(promises).then(() => {
    console.log('Map Opration Successfully Completed')
})

Example App:

import React, { useState } from 'react'
import { View, StatusBar, Text, Button } from 'react-native'

export default function App() {

    const [testUserArray, setTestUserArray] = useState(testArray)

    const _testMap = async () => {

        var promises = testUserArray.map((item, index) => {
            console.log('item', item)
        })

        await Promise.all(promises).then(() => {
            console.log('Map Opration Successfully Completed')
        })
    }
    return (
        <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', }}>
            <Button
                title='Test .map()'
                onPress={() => _testMap()}
            />
        </View>
    )
}

const testArray = [
    {
        "id": '1',
        "name": 'User 1'
    },
    {
        "id": '2',
        "name": 'User 2'
    },
    {
        "id": '3',
        "name": 'User 3'
    },
    {
        "id": '4',
        "name": 'User 4'
    },
    {
        "id": '5',
        "name": 'User 5'
    },
]
Hardik Desai
  • 1,089
  • 12
  • 20
0

Just check the length of your map array and compare it with map key.

Use async await to wait till file to upload

var mediaArray = []; //array of success callbacks values
var completedMediaSurveysAnswers = [{}, {}, {}]; //object array of URLs and media types
completedMediaSurveysAnswers.map(async (i, key) => {
  try {
    const storage = firebase.storage();
    const mRef = storage.ref('portal').child('Survey/Image/user/' + uuidv4() + 'media');
    await mRef.putFile(i.urlPath, {
        contentType: i.mediaType
      })
      .on('state_changed', snapshot => {},
        err => {
          console.log('Failed to upload file to firebase storage')
        },
        uploadedFile => {
          // Success
          this.setState({
            mediaPath: uploadedFile.downloadURL
          })
          mediaArray.push(this.state.mediaPath)
        });

     if( key == (completedMediaSurveysAnswers.length - 1 ) ){
       saveAnswers(mediaArray)
     }
  } catch (error) {
    console.log(error)
  }
})

I don't tested async await so use it wherever its set.

Vibha Chosla
  • 733
  • 5
  • 12