Как выполнить Join в Firebase, используя Node.js? - PullRequest
0 голосов
/ 29 марта 2020

У меня есть база данных с диспансером и соответствующим местоположением.

Database layout

Моя цель - вернуть все диспансеры и их соответствующие местоположения. Я пытался сделать это следующим образом

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import { Dispensary } from './model/Dispensary';
import { Location } from './model/Location';

export const getDispensaries = functions.https.onRequest(async (req, res) => {
    let dispensaries: Dispensary[] = []
    await admin.database().ref("/dispensary").once("value")
      .then(function(snapshot) {
        snapshot.forEach(function(childSnapshot) {

            var dispensaryValues = childSnapshot.val();
            var name =  dispensaryValues["name"];
            var locationID = dispensaryValues["locationID"];

            admin.database().ref("/location").child(locationID).once("value")
                .then(function(innerSnapShot) {
                    innerSnapShot.forEach(function(innerChildSnapShot) {

                        var locationValues = innerChildSnapShot.val();
                        var latitude = locationValues["latitude"];
                        var longitude =  locationValues["longitude"];

                        let location: Location = new Location(latitude, longitude);
                        let dispensary: Dispensary = new Dispensary(name, location);
                        dispensaries.push(dispensary);
                    });
                }).catch(error => {
                    console.log(error);
            });
      });
    }).catch(error => {
        console.log(error);
    });
    res.send(dispensaries)
});

, и мои классы Dispensary и Location выглядят так

export class Dispensary {
    name: string;
    location: Location;

    constructor(name: string, location: Location) {
        this.name = name;
        this.location = location;
    }
}

export class Location {
    latitude: number;
    longitude: number;

    constructor(latitude: number, longitude: number) {
        this.latitude = latitude;
        this.longitude = longitude;
    }
}

Теперь этот код возвращает мне пустой массив []. Я попытался получить только диспансеры, и это работает, но когда я удаляю асинхронный вызов / ожидание из вызова диспансера, он снова возвращает пустой массив.

Кроме того, когда я пытаюсь добавить ожидание в вызов местоположений, он говорит мне Мне не хватает асин c, но когда я добавляю асин c, это говорит мне Type Promise<void> is not assignable to type 'void'

Подхожу ли я к этому совершенно неверным способом? Спасибо за любую помощь, спасибо!

1 Ответ

0 голосов
/ 29 марта 2020

Ваша основная проблема заключалась в том, что вы возвращали значение dispensaries, не дожидаясь вначале каждого местоположения, которое будет извлечено, проанализировано, собрано и затем добавлено в массив dispensaries.

Приведенный ниже код выполняется посредством правильного потока операций для объединения двух местоположений.

export const getDispensaries = functions.https.onRequest((req, res) => {
    // STEP: Define reused database locations
    const DISPENSARY_REF = admin.database().ref("/dispensary");
    const LOCATION_REF = admin.database().ref("/location");

    // STEP: Fetch list of dispensaries
    DISPENSARY_REF.once("value")
        .then(function (allDispensariesSnapshot) {
            // STEP: Define array to store all tasks so they can be run in parallel
            const dispensaryPromises: Promise<Dispensary>[] = [];

            // DataSnapshot.forEach() is syncronous - so we can't use async/await syntax here
            // STEP: Iterate over each dispensary in the database
            allDispensariesSnapshot.forEach(function (dispensarySnapshot) {
                // STEP: Extract dispensary data
                // Ref: /dispensary/someDispensaryID
                // Available Data: { locationID, name }
                const dispensaryValues = dispensarySnapshot.val();
                const { locationID, name } = dispensaryValues;

                // Fetch location data & resolve with completed dispensary object
                // STEP: Fetch needed data from other database locations
                const currentDispensaryPromise = LOCATION_REF.child(locationID).once("value")
                    .then(function (locationSnapshot) {
                        // STEP: Extract data
                        // Ref: /location/someLocationID
                        // Available Data: { latitude, longitude }
                        const locationValues = locationSnapshot.val();
                        const { latitude, longitude } = locationValues;

                        // STEP: Assemble objects
                        const location: Location = new Location(latitude, longitude);

                        // STEP: Return the final object
                        return new Dispensary(name, location);
                    });

                // STEP: Add this task to list of tasks
                dispensaryPromises.push(currentDispensaryPromise);
            });

            // STEP: wait for all tasks
            return Promise.all(dispensaryPromises);
        })
        .then(dispensaries => {
            // Objects were retrieved successfully
            // STEP: Return data to client
            res.json(dispensaries);
        })
        .catch(error => {
            // Something went wrong
            // STEP: Log & return error to client
            console.log(error); // log full error here
            res.status(500).json({error: error.message}); // but only return error message to client
        });
});

Если на более позднем этапе вам нужно выбрать более одного местоположения, вы обновите currentDispensaryPromise до:

// Fetch other data & resolve with completed dispensary object
// STEP: Fetch needed data from other locations
const currentDispensaryPromise = Promise.all([
    LOCATION_REF.child(locationID).once("value"),
    USER_REF.child(ownerID).child('name').once("value") // with locations that have lots of data, fetch only what's needed
])
    .then(([locationSnapshot, ownerNameSnapshot]) => {
        // STEP: Extract data
        // Ref: /location/someLocationID
        // Available Data: { latitude, longitude }
        const locationValues = locationSnapshot.val();
        const { latitude, longitude } = locationValues;

        // Ref: /user/someOwnerID/name (string)
        const ownerName = ownerNameSnapshot.val();

        // STEP: Assemble objects
        const location: Location = new Location(latitude, longitude);
        const owner: Owner = new Owner(ownerID, ownerName);

        // STEP: Return the final object
        return new Dispensary(name, location, owner);
    });

Примечание: Если такое сочетание данных диспансера и местоположения используется чаще, чем нет, рассмотрите возможность объединения объектов вместе, а не их хранения отдельно.

Другие примечания

Нет необходимости использовать объекты для Dictionary и Location, достаточно просто указать форму объекта как тип.

type Dispensary = {
    name: number;
    location: Location;
}

type Location = {
    latitude: number;
    longitude: number;
}

const {name, locationID} = dispensarySnapshot.val();
const location = locationSnapshot.val() as Location;
return {name, location} as Dispensary;

Однако, если вы хотите сохранить их как объекты, вы можете сгладить ваши определения:

class Dispensary {
    constructor(public name: string, public location: Location) {}
}

class Location {
    constructor(public latitude: number, public longitude: number) {}
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...