Node Express + firebase-admin 服务器 - POST 中的 firebase 数据库参考在 GET 中调用代码 - 为什么?

Node Express + firebase-admin server - firebase database reference in POST calling code in GET - why?

我花了很多时间调试这个问题,我得出的结论是它与我在 POST 和 express 服务器的 GET 路由中使用的 firebase 数据库引用有关。以下是完整的代码。我的前端调用的第一个路由是 router.get('/get-images', function(req, res) ... 请注意,在回调函数中我首先设置了引用 firebaseApp.database().ref(/user_profile_images/${userId})。此代码从头到尾运行良好。

但是后来我调用了 POST 路由 router.post('/add-images', upload.single('userImage'), function(req, res) ...,这就是事情开始变得非常奇怪的地方 - 一旦代码执行到了使用 Firebase 引用的地步 firebaseApp.database().ref(user_profile_images/${userId}).push()... 似乎在这一点上调用了之前在 GET 路由中首次实现的回调函数,这就是我完全不知道的地方到底是怎么回事。

以下是我的完整服务器代码,然后是示例输出,其中我设置了 console.logs 到 'map' 函数中的每个步骤 - 注意当行从 ADD IMAGES 部分更改时的输出5 到 GET IMAGES 第 1 部分 - 这是来自 POST 的执行代码似乎在 GET 路由

中调用参考回调中的代码的要点
ADD IMAGES part 4
ADD IMAGES part 5
GET IMAGES part 1
GET IMAGES part 2

完整服务器代码(减去有效 keys/db names/etc):

let express = require('express');
let cors = require('cors');
let bodyParser = require('body-parser');
const uuidv4 = require('uuid/v4');
let base64Img = require('base64-img');
var firebaseAdmin = require("firebase-admin");
let cloudinary = require('cloudinary');
let multer = require('multer');
let UserProfileImage = require('./utils/UserProfileImage');
let _ = require('lodash');


/**
 *  ========================
 * firebase set up
 *
 * @type       {Function}
 */
var firebaseServiceAccount = require('./somekeyfile.json');

let firebaseApp = firebaseAdmin.initializeApp({
  credential: firebaseAdmin.credential.cert(firebaseServiceAccount),
  databaseURL: 'https://somedb.firebaseio.com/'
});
// ========================


/**
 * ========================
 * couldinary configuration
 */
cloudinary.config({ 
  cloud_name: 'somecloudname', 
  api_key: 'somekey', 
  api_secret: 'somesecret' 
});

const CLOUDINARY_UPLOAD_PRESET = 'veeezmct';
let port = process.env.PORT || 8080;
// ========================



// ROUTES FOR OUR API
// ===================================



// get an instance of the express Router
let router = express.Router();


// test route to make sure everything is working
// (accessed at POST http://localhost:8080/image-controller-api/upload)
let upload = multer({
    dest: 'uploads/',
    limits: {
        fileSize: 5 * 1024 * 1024 // no larger than 5mb, you can change as needed.
    }
});
router.post('/add-images', upload.single('userImage'), function(req, res) {

// console.log("\n########################\n########################\n########################\n");
console.log('ADD IMAGES');

// make sure there is a user id before continuing
if (req.body.userId === undefined || req.file === undefined) {
    return res.json({message: 'User ID and image file required to proceed'});
}

console.log('ADD IMAGES part 2');

// get the FB userId fromt he request body
const userId = req.body.userId;
const position = req.body.position;
const isPrimary = req.body.isPrimary;
let file = req.file;

console.log('ADD IMAGES part 3');

let uploadType = 'authenticated';
cloudinary.v2.uploader.upload(
    req.file.path,
    {
        upload_preset: CLOUDINARY_UPLOAD_PRESET,
        type: uploadType,
        sign_url: true,
        folder: userId
    },
    function(error, result) {

        console.log('ADD IMAGES part 4');

        // need to save the image url in the firebase
        // user record here - create a new user profile image
        //let ref = firebaseApp.database().ref(`user_profile_images/${userId}`);
        let userProfileImage = new UserProfileImage();
        userProfileImage.resourceId = result.public_id;
        userProfileImage.userId = userId;
        userProfileImage.url = result.secure_url;
        userProfileImage.position = position;
        userProfileImage.isPrimary = isPrimary;
        userProfileImage.isPrivate = "false";

        console.log('ADD IMAGES part 5');


        // use 'child' and 'set' combination to save data
        // in your own generated key
        firebaseApp.database().ref(`user_profile_images/${userId}`)
            .push().update(userProfileImage).then(function(ref1) {
                console.log('ADD IMAGES part 6');

                base64Img.requestBase64(
                    result.secure_url,
                    function(err, messageRes, body) {
                        console.log('ADD IMAGES part 7');

                        if (!err) {
                            console.log('SUCCESS ENCODING IMAGE');
                            return res.json({imageData: body});
                        } else {
                            console.log('ERROR ENCODING IMAGE');
                            return res.json({error: err});
                        }

                    }
                );


        }, function(error) {
            console.log('ERROR AT END', error);
        });

    }
);

});


/**
 * get user profile images by user ID
 **/
router.get('/get-images', function(req, res) {

console.log("\n///////////////////////////////////////\n///////////////////////////////////////\n///////////////////////////////////////\n///////////////////////////////////////\n");

console.log('GET IMAGES');

let userId = req.query.userId;
firebaseApp.database().ref(`user_profile_images/${userId}`).on(
        'value',
        snapshot => {

            console.log('GET IMAGES part 1');

            // if we have valid objects to iterate through
            if (snapshot.val()) {
                console.log('GET IMAGES part 2');

                // iterate through the objects to get each image url
                // and base64 representation

                // get the userProfileImages object list
                let userProfileImages = snapshot.val();
                // flag to determine how many requests have been completed
                let completeRequests = 0;
                // the total number of request to make which is the total
                // number of userProfileImages to process
                let numberOfRequestsToMake = Object.keys(userProfileImages).length;

                // iterate through each of the user profile images
                console.log('GET IMAGES part 3');
                _.map(userProfileImages, (userProfileImage, key) => {

                    console.log('GET IMAGES LOOP ', completeRequests);
                    completeRequests++;

                    if (completeRequests === numberOfRequestsToMake) {
                        console.log('GET IMAGES part 4');
                        console.log('# of requests complete, return to client');
                        return res.json({ userProfileImages: userProfileImages });
                    }

                }); // end _.map

            } else {
                console.log('ok sending default empty array');
                return res.json({userProfileImages: {}});
            }

        },
        errorObject => {
            console.log('base64Img.requestBase64 result error');
            return res.json({error: error});
        }
    );

});


// set up the server
let app = express();

app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());


// REGISTER OUR ROUTES
// all of our routes will be prefixed with /image-controller-api
app.use('/image-controller-api', router);


// START THE SERVER
app.listen(port);
console.log('Listening...');

这是我代码中每个 console.log() 的命令行输出:

//////////////////////////////////////
///////////////////////////////////////
///////////////////////////////////////
///////////////////////////////////////

GET IMAGES
GET IMAGES part 1
GET IMAGES part 2
GET IMAGES part 3
GET IMAGES LOOP  0
GET IMAGES LOOP  1
GET IMAGES LOOP  2
GET IMAGES LOOP  3
GET IMAGES LOOP  4
GET IMAGES part 4
# of requests complete, return to client
ADD IMAGES
ADD IMAGES part 2
ADD IMAGES part 3
ADD IMAGES part 4
ADD IMAGES part 5
GET IMAGES part 1
GET IMAGES part 2
GET IMAGES part 3
GET IMAGES LOOP  0
GET IMAGES LOOP  1
GET IMAGES LOOP  2
GET IMAGES LOOP  3
GET IMAGES LOOP  4
GET IMAGES LOOP  5
GET IMAGES part 4
# of requests complete, return to client
FIREBASE WARNING: Exception was thrown by user callback. Error: Can't set headers after they are sent.
at ServerResponse.setHeader (_http_outgoing.js:371:11)

重要说明:如果我从未在我的应用程序的生命周期中调用 GET 路由,那么 POST 路由会正常执行直至完成....

问题是这个位:

firebaseApp.database().ref(`user_profile_images/${userId}`).on(
        'value',

您正在为图像的更改注册一个侦听器,但从未删除它。最初将使用当前值调用侦听器,但随后每次值更改时都会调用它。您的 POST 处理程序更改了值以触发事件。

要解决此问题,请使用 once 而不是 on