본문 바로가기

일::개발

Firestore 두 개의 collection에서 연관 데이터 조회. 비동기 처리. javascript

Firestore를 처음 써봤는데, 간단하다고 생각했던 문제를 처리하는데 의외로 오래 걸려서 정리해둔다.

하나의 collection에서 데이터 조회한 후에 그 값을 키로 다른 collection에 있는 데이터를 읽어와서 JSON array를 만드는 작업이었다.

아주 간단한 일인데, 두번째 collection의 조회가 끝날 때까지 기다렸다가 json array 만드는 것이 잘 안 됐다.

 

Collection 1 : channels {

  "chname1": {

    "group": 1,

    "order": 1,

    "name": "채널1"

  },

  "chname2": {

    "group": 1,

    "order": 2,

    "name": "채널2"

  }, ....



Collection 2: schedules {

  "chname1": {

    "title": "프로그램1",

  },

  "chname2": {

    "title": "프로그램2",

  }, ...

대략 이렇게 되어 있고, channels 에서 검색하여 각각에 대해 schedules 정보를 merge해서 JSON array 를 만든다고 가정한다.

 

작업 순서는 

1) channels 검색 : db.collection("channels").where("group", "==", 1).orderBy("order", "asc").get()

2) 검색 결과 iterate하며 schedules 검색 db.collection("schedjles").doc(ch.id).get()

3) 검색 결과 합쳐서 JSON array 구성

 

첫번째 시도했던 코드는 대략 이렇다.

// using express

app.get("/chs", async (req, res) => {
	var ret = [];
	const chs = await db.collection("channels").where("group", "==", 1).orderBy("order", "asc").get();
    
    chs.forEach(async (ch) => {
    	const schedule = await db.collection("schedules").doc(ch.id).get();
        ret.push({
        	id: ch.id,
            title: schedule.data().title
        });
    });
    
    return res.json(ret);
});

기대하는 결과는 

[{id: "chname1", title:"프로그램1"}, {id: "chname2", title: "프로그램2"}...]

였지만 실제 결과는

[]

 

chs.forEach() 안의 부분이 실행되기 전에 빈 array를 return해버린 것이다.

forEach() 는 순차적으로 callback을 실행만 할 뿐, callback의 완료를 보장하지 않기 때문이다.

 

가장 간단한 해결 방법은 forEach()를 for()로 변경하는 것이다.

    const ret = [];
    const chs = await db.collection("channels").where("group", "==", 1).orderBy("order", "asc").get();

    for (const i in chs.docs) {
      const doc = chs.docs[i];
      const schedule = await db.collection("schedules").doc(doc.id).get();
      ret.push({
        id: doc.id,
        title: schedule.data().title,
      });
    }
    
    return res.json(ret);

해보는 김에 schedules 조회를 비동기로 처리하고 싶다면 Promise를 사용해서 이렇게도 할 수 있다.

 

    const promises = [];
    const chs = db.collection("channels").where("group", "==", 1).orderBy("order", "asc").get();

    chs.forEach((ch) => {
      promises.push(db.collection("schedules").doc(ch.id).get().then(function(schedule) {
        return {
          id: ch.id,
          title: schedule.data().title,
        };
      }));
    });
    
    Promise.all(promises).then((ret) => {
      res.json(ret).end();
    }).catch((error) => res.json({"result": error}));