【问题标题】:How do I wait for a .map() with asynchronous calls to finish before moving on?在继续之前,如何等待具有异步调用的 .map() 完成?
【发布时间】:2018-01-18 15:14:22
【问题描述】:

我有一个 Picture 对象数组。我使用.map() 对它们进行迭代,因此我可以将它们中的每一个上传到我的firebase-storage。 Picture 的上传完成后,我想获取Picture 的 URL 并将其放入另一个数组中。

一旦完成所有上传,并且我拥有完整的 URL 数组,我想将 db.push() 数据发送到我的数据库。

问题是db.push() 开始之前我所有的图片都上传完成了!

如何让db.push() 等到.map() 正确完成?

    UploadForm(event) {
          event.preventDefault();
          const db = firebase.database().ref('app').child('cards');
          const userId = this.props.user;

          //the stuff from the Form
          const titel = this.titelInput.value;
          const grabtiefe = this.grapTiefeVonInput.value +" - " + this.grapTiefeBisInput.value;
          const transportbreite = this.transportbreiteVonInput.value + " - " + this.transportbreiteBisInput.value;
          const transporthoehe = this.transporthoeheVonInput.value + " - " + this.transporthoeheBisInput.value;
          const gewicht = this.GewichtdesArtikelsInput.value;
          const preis = this.priceInput.value;
          const desc = this.descInput.value;
          const adresse = this.props.address;
          const ort = this.props.ort;
          const mobil = this.props.mobil;
          const festnetz = this.props.festnetz


          const array = []
          const pictures = this.state.pictures[0]
          const keys = Object.keys(pictures)

          //the map function start to upload an Array of Pics and gets its URL and Pushes it to an Array. 
          // after this the map funciton sets the array to this.state.Arr  
          keys.map((key) => {
              const picture = pictures[key]
              firebase.storage().ref('images').child('artikelimgaes/'+userId).child('artikel/')
              .child(titel).child(picture.name).put(picture)
              .then(()=>{
                firebase.storage().ref('images').child('artikelimgaes/'+userId).child('artikel/').child(titel).child(picture.name)
                .getDownloadURL().then((url) => {
                  const imgUrl = url;
                  array.push(url)
                  this.setState({Arr : array})
                })
              })
          })

          // now the Upload of all Data should start 
            const coordinaten = this.state.cords;
            const images = this.state.Arr
            const mainImage = this.state.Arr[0]
            db.push({
                      grabtiefe: grabtiefe,
                      transportbreite: transportbreite,
                      transporthoehe: transporthoehe,
                      cardHeading:titel ,
                      cardPreis: preis,
                      cardDesc: desc,
                      gewicht: gewicht,
                      address: adresse,
                      ort: ort,
                      gemietet: 0,
                      cords: coordinaten,
                      mobil:mobil ,
                      festnetz: festnetz,
                      imageArr: images,
                      imageUrl: mainImage,
                      gebiet: this.state.gebiet,
                      bundesland: this.state.bundesland,
                      uid: userId,

                    })
                    this.setState({
                      redirect: true
                    })

                }

      } 

【问题讨论】:

  • return firebase 承诺,他们将映射的承诺数组包装成Promise.all

标签: javascript reactjs function firebase ecmascript-6


【解决方案1】:

这个问题有很多可能的解决方法。

你可以把上传函数放在setState回调中

this.setState({Arr : array}, () => {
  // now the Upload of all Data should start 
} )

使用承诺编辑

let keysPromises = keys.map(
  key =>
    new Promise((resolve, reject) => {
      const picture = pictures[key];
      firebase
        .storage()
        .ref("images")
        .child("artikelimgaes/" + userId)
        .child("artikel/")
        .child(titel)
        .child(picture.name)
        .put(picture)
        .then(() => {
          firebase
            .storage()
            .ref("images")
            .child("artikelimgaes/" + userId)
            .child("artikel/")
            .child(titel)
            .child(picture.name)
            .getDownloadURL()
            .then(url => {
              const imgUrl = url;
              array.push(url);
              this.setState({ Arr: array }, resolve());
            });
        });
    })
);

Promise.all(keysPromises).then(() => {
  // now the Upload of all Data should start
});

【讨论】:

  • 感谢您回答我,但这是在地图功能中,因此只要数组中有要映射的内容,上传就会开始,或者?
  • 非常感谢您的帮助。
【解决方案2】:

您正在对put()getDownloadURL() 执行多次调用,并且您希望等到所有这些调用都处理完毕后再继续执行其他工作。

如果这些调用是同步的,那就没问题了,也就是说,执行会一直等到函数完成后再继续,但事实并非如此。

你可以知道,因为它们都返回一个promise,这意味着它们是异步的;因此,无需等待即可继续执行。

这就是问题所在。我们如何解决它?

这里的诀窍是使用 JS Promise 的一个名为 Promise.all() 的特性。这需要一组 Promise 并返回它自己的一个 Promise。一旦 promise 数组全部解析(或其中一个已被拒绝),则单个复合 promise 被解析或拒绝。

如果您使用的是现代 JS,那么使用新的 async / await 语法可以轻松实现这一点。

您将重新编写您的代码,使其看起来像这样:

警告:未经测试的代码

const getFirebase = pictureName =>
  firebase
    .storage()
    .ref('images')
    .child('artikelimgaes/' + this.props.user)
    .child('artikel/')
    .child(titel)
    .child(pictureName)

const getImageUrls = async keys => {
  const pictures = this.state.pictures[0]
  const keys = Object.keys(pictures)
  const picture = pictures[key]
  await Promise.all(keys.map(key => getFirebase(picture.name).put(picture)))
  await Promise.all(keys.map(key => getFirebase(picture.name).getDownloadURL(picture)))
}

const UploadForm = async event => {
  event.preventDefault()
  const db = firebase
    .database()
    .ref('app')
    .child('cards')

  //the stuff from the Form
  const titel = this.titelInput.value
  const grabtiefe = this.grapTiefeVonInput.value + ' - ' + this.grapTiefeBisInput.value
  const transportbreite = this.transportbreiteVonInput.value + ' - ' + this.transportbreiteBisInput.value
  const transporthoehe = this.transporthoeheVonInput.value + ' - ' + this.transporthoeheBisInput.value
  const gewicht = this.GewichtdesArtikelsInput.value
  const preis = this.priceInput.value
  const desc = this.descInput.value
  const adresse = this.props.address
  const ort = this.props.ort
  const mobil = this.props.mobil
  const festnetz = this.props.festnetz

  // get all the picture urls and save them to state.
  this.setState({ Arr: await getImageUrls() })

  // now the Upload of all Data should start
  const coordinaten = this.state.cords
  const images = this.state.Arr
  const mainImage = this.state.Arr[0]
  db.push({
    grabtiefe: grabtiefe,
    transportbreite: transportbreite,
    transporthoehe: transporthoehe,
    cardHeading: titel,
    cardPreis: preis,
    cardDesc: desc,
    gewicht: gewicht,
    address: adresse,
    ort: ort,
    gemietet: 0,
    cords: coordinaten,
    mobil: mobil,
    festnetz: festnetz,
    imageArr: images,
    imageUrl: mainImage,
    gebiet: this.state.gebiet,
    bundesland: this.state.bundesland,
    uid: this.props.user
  })
  this.setState({
    redirect: true
  })
}

使用getImageUrls 函数,我将您的单个map 分成两部分。第一个map 负责将所有图片放入firebase。每个 put 返回一个 Promise,map 返回一个 Promise 数组。然后这个数组是awaited,即代码执行等待,直到所有的承诺都得到解决。我们现在可以通过调用getDownloadURL() 来做同样的事情。

请注意,如果您想在函数中使用await 关键字,则该函数必须标记为async。您可以在 getImageUrls 函数中看到这种情况。

当一个函数被标记为async 时,无论函数内部发生什么,它总是会返回一个promise。这个 implicit 承诺仅在函数完成时自动解决,在这种情况下,当两个数组中的所有承诺都已解决时。它正在等待,因为我们正在等待对 Promise.all() 的两个调用

所以现在我们需要确保 async getImageUrls() 函数返回的单个隐式承诺已经解决,然后再继续进行其他工作。所以我们也需要await这个promise,这意味着我们需要将UploadForm`函数标记为异步以允许这样做。

最终结果应该是从 getImageUrls() 函数返回的一个不错的图片Urls 数组。您现在可以在继续进行数据库工作之前使用此数组设置您的状态,因为您知道所有图片都已安全地存储在 firebase 中。

【讨论】:

  • 哇,我很惊讶。非常感谢你向我解释了这一切。这个社区的友善程度让我有点不知所措
猜你喜欢
  • 2011-02-15
  • 1970-01-01
  • 1970-01-01
  • 2018-11-28
  • 1970-01-01
  • 1970-01-01
  • 2017-11-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多