Node JS - adatok összegyűjtése response előtt

Node JS - adatok összegyűjtése response előtt
2016-12-15T15:51:53+01:00
2017-01-04T14:33:10+01:00
2022-10-15T22:46:01+02:00
Axxtros
Sziasztok,

Az lenne a feladat, hogy a response kiküldés előtt, asszinkron módon - adatbázisból - a (lenti kódban) található testArray tömböt fel tudjam tölteni adatokkal. Az adatokat nem akarom közvetlenűl a renderedben felhasználni, ráaádsul jó lenne, ha ezt nem kellene minden egyes oldal respons-ában megírni. hanem oda csak ezt meghívom, mint "univerzális" betöltő függvényt.
Az adatok beolvasása működik, a probléma az, hogy az adatbázisból való beolvasás minidg csak már a response után hajtódik végre, ami rossz.

Ez lenne a betöltő osztály.

import * as databaseControl from "../modules/db"; export namespace DatabaseControlNameSpace { var async = require('async'); var dbCtrl = new databaseControl.DatabaseControlNameSpace.DBControl(); export class ApplicationClass { private dbCtrl :databaseControl.DatabaseControlNameSpace.DBControl; private testArray: Array<string>; // = ['element 0', 'element 2']; //ez most nincs használva private pageData: { key: string, value: string }[] = [ { "key": "-..-", "value": "..-.." } ]; constructor() { console.log('@ApplicationClass constructor'); this.pageDataLoader(); } private pageDataLoader(): void { console.log('@ApplicationClass pageDataLoader()'); this.testArray = new Array(); this.testArray.push('element 1'); this.testArray.push('element 2'); //ez működik for (let i = 3; i != 6; i++) { this.testArray.push('element ' + i); } for (let item in this.testArray) { console.log('@testArray: ' + ' ' + this.testArray[item]); } //ez működik, de a GET után fut le let pr_name = dbCtrl.getProgramName(function getPrName(err, result) { console.log('@getProgName: ' + result); //this.testArray.push('element' + result); }); //ez működik, de a GET után fut le async.parallel({ func_prog_name: function asyncParallalDbGetProgName(callback) { dbCtrl.getProgramName(function getProgramProgNameCallback(err, result) { console.log('@async 1'); callback(err, result); }); }, func_prog_ver: function asyncParallalDbGetProgName(callback) { dbCtrl.getProgramVersion(function getProgramProgNameCallback(err, result) { console.log('@async 2'); callback(err, result); }); }, }, function asyncParallalResulthandler(err, results) { if (err) { console.log('@async 3 - ERROR'); } else { console.log('@async 4 - OK'); //this.testArray.push('egy'); //this.testArray.push('kettő'); } }); } //getters/setters public set _testArray(_a: string[]) { this.testArray = _a; } public get _testArray(): string[] { return this.testArray; } } }
Ez lenne a hívó / renderelő, amely a tényleges választ küldi vissza:

import express = require('express'); var router = express.session(); //Router(); var app = express(); var async = require('async'); //var appModule = require('../modules/application'); import * as appControl from "../modules/application"; exports.game = function (req, res, next) { //var pageDataObj = new appModule.DatabaseControlNameSpace.ApplicationClass(); let appCtrl = new appControl.DatabaseControlNameSpace.ApplicationClass(); console.log('@game.ts'); for (let item in appCtrl._testArray) { console.log('@appCtrl._testArray: ' + appCtrl._testArray[item]); } /* for (let item in appCtrl.pageDataArray) { console.log('@appCtrl pageDataArray' + appCtrl.pageDataArray[item].key + ' ' + appCtrl.pageDataArray[item].value); } */ res.render('game.ejs', { test_a: appCtrl._testArray[0], t_array: appCtrl._testArray }); }
A konzol log-ból kiderül a hívások sorendje:

@ApplicationClass constructor
@ApplicationClass pageDataLoader()
@testArray: element 1
@testArray: element 2
@testArray: element 3
@testArray: element 4
@testArray: element 5
@game.ts
@appCtrl._testArray: element 1
@appCtrl._testArray: element 2
@appCtrl._testArray: element 3
@appCtrl._testArray: element 4
@appCtrl._testArray: element 5
GET / 304 86ms
@getProgName: Ez DB-ből jött!
@async 1
@async 2
@async 4 - OK

A kérdésem tehát az lenne, hogy hogyan kell átalakítani a kódot, hogy az async részek is lefussanak még a GET előtt? Kösz. :)
Mutasd a teljes hozzászólást!
Each helyett esetleg all ?

mapbox/node-sqlite3
(Database#each, Database#all)
Mutasd a teljes hozzászólást!

  • Az async,parallel nem vár, rögtön visszatér, és lefut a handlered vége, benne a res.render-rel. Utána majd nemsokára a különféle taszkok végrehajtásra kerülnek, és lefut az async.parallel végére tett (opcionális) callback.
    Megoldás: mindent, amit a párhuzamos taszkok lefutása utánra szánsz, a callbackba kell csomagolni. Mivel JavaScript-ben vannak lexical closure-ok (van magyar neve is, egyszer láttam valahol, aztán soha többet, és persze nem emlékszem rá), ezért gyakorlatilag a hívó kód végét bekapcsoszárójelezheted egy függvénybe, és átadhatod paraméterként a lekérdező függvénynek, ami majd meghívja a callbackon belül, vagy a callback helyett használja.

    Megj: a kommentjeid szerint mindez a dbCtrl.getProgramName hívásra is igaz lehet, ez esetben magát az async.parallel-es kódrészletet is a dbCtrl.gerProgramName callbackjába (getPrName) kéne csomagolni.

    Szóval az általános minta:

    var x=0; varazslat(function majdsokara(){x=10;}); console.log("Remelem 10: "+x+ "desajnos0");
    helyett
    var x=0; varazslat(function majdsokara(){x=10;console.log("Remelem 10: "+x+" estenyleg");}
    Ami bonyolultabb esetben
    var x=0; function varazsolj() { varazslat(function majdsokara(){x=10;}); } ... varazsolj(); console.log("Remelem 10: "+x+"desajnos0");
    helyett
    var x=0; function varazsolj(vegere) { varazslat(function majdsokara(){x=10;vegere();}); } ... varazsolj(function(){console.log("Remelem 10: "+x+" estenyleg");});
    lesz.
    Ha a varazslat(function majdsokara(){...}) helyett setTimeout(function majdsokara(){...},10000); hívást képzelsz, akár ki is próbálhatod.
    Mutasd a teljes hozzászólást!
  • Nem akartam új témát nyitni, ezért a korábbi problémát tovább vizsgálva egy olyan hibába ütköztem, amelyet nem igazán értek. Ha a lekérdezés az adatbázisból több rekordot ad vissza, akkor Callback was already called hibaüzenetet kapok, azonban ha where-t beleteszem - és így biztosan csak egy rekordot ad vissza - akkor rendben lefut kód, és minden adatot megkapok.
    (Az async blokkot próbáltam parallel-el, waterfall-al, each-el, illetve series-el is, nem működik.)

    Tehát az async blokk:

    async.series({ getWebpageStaticDatas: function _getWebpageStaticDatas(callback) { dbCtrl.getWebPageDatas(function getWebpageDatasCallback(err, result) { console.log('@async result id: ' + result.id + ' key: ' + result.key + ' value: ' + result.value); setTimeout(function () { if (err) { console.log('@async getWebpageStaticDatas ERROR: ' + err.message); } else { storage.DataStorageNameSpace.BaseClass.addItem(result.key, result.value); //OK } callback(err, result); }, 1000); }); } }, function asyncParallalResulthandler(err, results) { if (err) { console.log('@async 3 - ERROR: ' + err.message); } else { console.log('@async 4 - OK'); console.log('@async 4 : results: ' + results.getWebpageStaticDatas.key); //Callback was already called. } })
    Az adatbázis lekérdező kód:

    getWebPageDatas(callback: any): any { //var sql = "select sp.id, sp.key, sp.value from sys_param sp"; //n rekord -> HIBA var sql = "select sp.id, sp.key, sp.value from sys_param sp where sp.id = 1"; //csak egy rekord -> OK dbase.each(sql, (err, rows) => { if (err) { console.log('@db err: ' + err); callback(err, null); } else { console.log('@db rows id: ' + rows.id + ' rows key: ' + rows.key + ' rows value: ' + rows.value); callback(null, rows); } }); }
    Ha csak egy rekordot kérek, akkor minden rendben, a konzolról ez látható is:

    @db rows id: 3 rows key: DEVELOPER rows value: ax @async result id: 3 key: DEVELOPER value: ax @async 4 - OK @async 4 : results: DEVELOPER
    Ha azonban több rekord jön fel, akkor a hibaüzenet:

    @db rows id: 1 rows key: PROGRAM_NAME rows value: Program Nev @async result id: 1 key: PROGRAM_NAME value: Program Nev @db rows id: 2 rows key: PROGRAM_VERSION rows value: 0.1 @async result id: 2 key: PROGRAM_VERSION value: 0.1 @db rows id: 3 rows key: DEVELOPER rows value: ax @async result id: 3 key: DEVELOPER value: ax @async 4 - OK @async 4 : results: PROGRAM_NAME @BaseClass addItem, key: PROGRAM_VERSION value: 0.1 C:\dev\nodeDemo1\node_modules\async\dist\async.js:837 if (fn === null) throw new Error("Callback was already called."); ^ Error: Callback was already called. at breakLoop (C:\dev\nodeDemo1\node_modules\async\dist\async.js:837:32) at C:\dev\nodeDemo1\node_modules\async\dist\async.js:3691:13 at apply (C:\dev\nodeDemo1\node_modules\async\dist\async.js:21:25) at C:\dev\nodeDemo1\node_modules\async\dist\async.js:56:12 at null._onTimeout (C:\dev\nodeDemo1\modules\application.js:119:33) at Timer.listOnTimeout (timers.js:92:15)
    Tehát a kérdésem az lenne, hogy mit és hogyan módosítsak ahhoz, hogy több rekord esetén ne kapjak Callback was already called hibaüzenetet. Az interneten csak néhány példát találtam rá, de azok sem ilyen esetben jönnek fel.
    Mutasd a teljes hozzászólást!
  • Szia!

    A "dbase.each(sql, (err, rows) => " sor miatt nyilván többször hívná meg a callback függvényed. Gyűjtsd össze amit kell és csak 1x hívd a callback-et.
    Mutasd a teljes hozzászólást!
  • Szia,

    Oké, ezt értem, hogy csak egyszer kellene, de azt pontosan hogyan valósítom meg kódszinten? :)
    Köszi.
    Mutasd a teljes hozzászólást!
  • Szerintem valami ilyesmire gondolt vberko

    getWebPageDatas(callback: any): any { var sql = "select sp.id, sp.key, sp.value from sys_param sp"; var rows = []; var errors = []; dbase.each(sql, (err, row) => { if (err) { errors.push(err); } else { rows.push(row); } }); if (errors.length === 0) { callback(null, rows); } else { callback(errors, null); } }
    Mutasd a teljes hozzászólást!
  • Each helyett esetleg all ?

    mapbox/node-sqlite3
    (Database#each, Database#all)
    Mutasd a teljes hozzászólást!
  • Igen, most találtam meg én is, és kipróbáltam és működik.

    getWebPageDatas(callback: any): any { var sql = "select sp.id, sp.key, sp.value from sys_param sp"; //var sql = "select sp.id, sp.key, sp.value from sys_param sp where sp.key = 'DEVELOPER'"; dbase.all(sql, function (err, rows) { if (err) { console.log('@db err: ' + err); callback(err, null); } else { console.log('@db rows id: ' + rows.id + ' rows key: ' + rows.key + ' rows value: ' + rows.value); callback(null, rows); } }); }
    Még tesztelem, de ha jó, akkor megy a pont.
    Mutasd a teljes hozzászólást!
Tetszett amit olvastál? Szeretnél a jövőben is értesülni a hasonló érdekességekről?
abcd