How to hold a NodeJS application until other promi

2019-07-02 12:55发布

问题:

Using promises with NodeJS, I load a model that can then be re-used by susequent calls to the NodeJS app. How can I prevent the same object/model being loaded twice from a database if a second request arrives while the first is still being loaded?

I set a "loading flag" to say that the object is being retrieved from the database and "loaded" when done. If there is a second request that attempts to load the same object, it needs to wait until the initial model is filled and then both can use the same object.

Sample Code (simplified, ES6, Node 0.10 [old for a reason]).

It's the TODO that needs solving.

App:

import ClickController from './controllers/ClickController'

import express from 'express'
const app = express()

app.get('/click/*', (req, res) => {

    // Get the parameters here
    let gameRef = "test";

    ClickController.getGameModel(gameRef)
        .then(() => {
            console.log('Got game model')
            return this.handleRequest()
        }, (err) => {
            throw err
        })
}

Controller:

import gameModel from '../models/GameModel'

class ClickController {

    constructor(config) {
        // Stores the objects so they can be re-used multiple times.
        this.loadedGames = {}
    }

    // getGameModel() as a promise; return model (or throw error if it doesn't exist)
    getGameModel(gameRef) {
        return new Promise((resolve, reject) => {
            let oGame = false
            if(typeof this.loadedGames[gameRef] === 'undefined') {
                oGame = new gameModel()
                this.loadedGames[gameRef] = oGame
            } else {
                oGame = this.loadedGames[gameRef]
            }

            oGame.load(gameRef)
                .then(function() {
                    resolve()
                }, (err) => {
                    reject(err)
                })
        })
    }
}

Model / Object:

class GameModel {

    constructor {
        this.loading = false
        this.loaded = false
    }

    load(gameRef) {
        return new Promise((resolve, reject) => {
            if (this.loading) {

                // TODO: Need to wait until loaded, then resolve or reject

            } else if (!this.loaded) {

                this.loading = true
                this.getActiveDBConnection()
                    .then(() => {
                        return this.loadGame(gameRef)
                    }, (err) => {
                        console.log(err)
                        reject(err)
                    })
                    .then(() => {
                        this.loading = false
                        this.loaded = true
                        resolve()
                    })
            } else {

                // Already loaded, we're fine
                resolve()
            }
        })
    }

    // As this uses promises, another event could jump in and call "load" while this is working
    loadGame(gameRef) {
        return new Promise((resolve, reject) => {

            let sql = `SELECT ... FROM games WHERE gameRef = ${mysql.escape(gameRef)}`

            this.dbConnection.query(sql, (err, results) => {
                if (err) {
                    reject('Error querying db for game by ref')

                } else if (results.length > 0) {

                    // handle results
                    resolve()

                } else {

                    reject('Game Not Found')
                }
            })

        })
    }
}

回答1:

I don't follow exactly which part of you're code you are asking about, but the usual scheme for caching a value with a promise while a request is already "in-flight" works like this:

var cachePromise;
function loadStuff(...) {
   if (cachePromise) {
       return cachePromise;
   } else {
       // cache this promise so any other requests while this one is stil
       // in flight will use the same promise
       cachePromise = new Promise(function(resolve, reject) {
          doSomeAsyncOperation(function(err, result) {
              // clear cached promise so subsequent requests
              // will do a new request, now that this one is done
              cachePromise = null;
              if (err) {
                  reject(err);
              } else {
                  resolve(result);
              }
          });
       });
       return cachePromise;
   }
}

// all these will use the same result that is in progress
loadStuff(...).then(function(result) {
   // do something with result
});

loadStuff(...).then(function(result) {
   // do something with result
});

loadStuff(...).then(function(result) {
   // do something with result
});

This keeps a cached promise and, as long as request is "in-flight", the cachePromise value is in place and will be returned by subsequent requests.

As soon as the request actually finishes, the cachePromise will be cleared so that the next request that comes later will issue a new request.