Promise先执行然后再执行功能,然后再执行之前

时间:2018-09-18 14:22:24

标签: javascript node.js firebase promise es6-promise

我试图链接几个依次执行的函数,但是最后一个.then()在前一个函数执行完毕之前就已经执行了,因此它发送了一个空的有效载荷。以下是代码段:

router.get("/selectedHotels", function(req, res) {
  let payload = [];
  return collectionRef
    .where("isOwner", "==", true)
    .get() //fetches owners
    .then(snapshot => {
      snapshot.forEach(user => {
        console.log("User", user);
        collectionRef
          .doc(user.id)
          .collection("venues")
          .get() // fetches hotels from owners
          .then(snapshot => {
            snapshot.forEach(doc => {
              if (
                doc.data().location.long == req.query.long &&
                doc.data().location.lat == req.query.lat
              ) {
                console.log(doc.id, "=>", doc.data());
                payload.push({
                  id: doc.id,
                  data: doc.data()
                });
              }
            });
          })
          .catch(err => {
            console.log("No hotels of this user", err);
          });
      });
    })
    .then(() => {
      console.log("Payload", payload);
      response(res, 200, "Okay", payload, "Selected hotels");
    })
    .catch(err => {
      console.log("Error getting documents", err);
      response(res, 404, "Data not found", null, "No data available");
    });
});

有什么建议吗?谢谢

4 个答案:

答案 0 :(得分:4)

您的主要错误是您在嵌套的承诺链中间有一个不承诺返回的函数forEach

router.get('/selectedHotels',function(req,res){ 
  let payload = [];
  return collectionRef.where(...).get()
    .then((snapshot)=>{
        snapshot.forEach(user => {
//      ^^^^^^^^^^^^^^^^^ this means the outer promise doesn't wait for this iteration to finish
// ...

最简单的解决方法是映射承诺数组,将其传递到Promise.all并返回它们:

router.get('/selectedHotels',function(req,res){ 
  let payload = [];
  return collectionRef.where(...).get()
    .then((snapshot)=> {
      return Promise.all(snapshot.map(
        // ...
        return collectionRef.doc(user.id).collection('venues').get()
          .then(...)
      ))

话虽如此,嵌套这样的承诺是一种反模式。承诺链允许我们通过then回调传播值,因此无需嵌套它们。

相反,您应该将它们垂直链接。

以下是您如何执行此操作的示例:

router.get("/selectedHotels", function(req, res) {
  return collectionRef
    .where("isOwner", "==", true)
    .get() //fetches owners
    // portion of the chain that fetches hotels from owners
    // and propagates it further
    .then(snapshot =>
      Promise.all(
        snapshot.map(user =>
          collectionRef
            .doc(user.id)
            .collection("venues")
            .get()
        )
      )
    )
    // this portion of the chain has the hotels
    // it filters them by the req query params
    // then propagates the payload array
    // (no need for global array)
    .then(snapshot =>
      snapshot
        .filter(
          doc =>
            doc.data().location.long == req.query.long &&
            doc.data().location.lat == req.query.lat
        )
        .map(doc => ({ id: doc.id, data: doc.data() }))
    )
    // this part of the chain has the same payload as you intended
    .then(payload => {
      console.log("Payload", payload);
      response(res, 200, "Okay", payload, "Selected hotels");
    })
    .catch(err => {
      console.log("Error getting documents", err);
      response(res, 404, "Data not found", null, "No data available");
    });
});

答案 1 :(得分:0)

.then链接到异步工作时,需要在执行下一个.then之前返回要解析的承诺。像这样:

    return Promise.all(snapshot.map(user => {
            console.log("User", user);
            return collectionRef.doc(user.id).collection('venues').get() // fetches hotels from owners
            .then(snapshot => {
                snapshot.forEach((doc)=> {
                        if (doc.data().location.long == req.query.long && doc.data().location.lat == req.query.lat){
                            console.log(doc.id, '=>', doc.data());
                            payload.push({
                                id: doc.id,
                                data: doc.data()
                            });
                        }
                    });
                }).catch((err)=>{
                    console.log('No hotels of this user', err);        
                });        
        });
    )

您可以在以下示例代码中看到它的作用:

function asyncStuff() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('async')
      resolve();
    }, 100)
  });
}

function doStuff() {
  console.log('started');
  asyncStuff()
  .then(() => {
    return Promise.all([0,1,2].map(() => asyncStuff()));
  })
  .then(() => {
    console.log('second then');
  })
  .then(() => console.log('finished'));
}

doStuff();

看到没有回报,它会给您最初的行为:

function asyncStuff() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('async')
      resolve();
    }, 100)
  });
}

function doStuff() {
  console.log('started');
  asyncStuff()
  .then(() => {
    Promise.all([0,1,2].map(() => asyncStuff()));
  })
  .then(() => {
    console.log('second then');
  })
  .then(() => console.log('finished'));
}

doStuff();

答案 2 :(得分:0)

您不会在第一个then内返回承诺,因此代码无法知道应该等待异步结果。

router.get('/selectedHotels',function(req,res){ 
    let payload = [];
    return collectionRef.where('isOwner', '==', true).get() //fetches owners
    .then((snapshot)=>{
        var userVenuesPromises = [];
        snapshot.forEach(user => {
            userVenuesPromises.push(collectionRef.doc(user.id).collection('venues').get());

        })
        return Promise.all(userVenuesPromises);
    })
    .then((snapshots) => {
        snapshots.forEach((snapshot) => {
            snapshot.forEach((doc)=> {
                if (doc.data().location.long == req.query.long && doc.data().location.lat == req.query.lat){
                    console.log(doc.id, '=>', doc.data());
                    payload.push({
                        id: doc.id,
                        data: doc.data()
                    });
                }
            });
        });
        return payload;
    })
    .then((payload) => {
        ...

除了使用Promise.all()确保在继续下一步之前完成所有嵌套加载外,这还删除了嵌套的Promise,而是在另一个步骤中从快照中解压缩了值。

答案 3 :(得分:0)

您使用的是Firestore,因此您需要提供所有要映射的文档,还需要向next返回一些值。希望这可以帮助您解决问题。

router.get('/selectedVenues',function(req,res){
    return collectionRef.where('isOwner', '==', true).get() 
    .then(snapshot => {
        let venues = [];
        snapshot.docs.map(user => {
            venues.push(collectionRef.doc(user.id).collection('venues').get());
        });
        return Promise.all(venues);
    }).then(snapshots => {
        let payload = [];
        snapshots.forEach(venues => {
            venues.docs
                .filter(doc => 
                    doc.data().longitude == req.query.lng && 
                    doc.data().latitude == req.query.lat
                )
                .map(doc => 
                    payload.push({
                        id: doc.id,
                        data: doc.data()
                    })
                ) 
        });
        return payload ;
    }).then(payload => {
        console.log('Payload', payload);
        response(res, 200, "Okay", payload, "Selected hotels");
    }).catch(err => {
        console.log('Error getting documents', err);        
        response(res, 404, 'Data not found', null, 'No data available');
    });
});