Schlimmes Verständnisproblem JavaScript functions return

Verzeiht meine totale Anfängerfrage, aber ich bin ratlos, und habe nicht viel mit JS zu tun…

app.get(bina_path, (req, res) => {
    let price_dict = [];
    all_pairs.forEach(n => {
        const name = n + 'USDT';
        binance.prices(name, (error, ticker) => {
            if (error) {
                console.error(error);
                price_dict.push({
                    name: name,
                    price: -1
                });
                return;
            }
            console.log(ticker);
            price_dict.push({
                name: name,
                price: ticker[name]
            });
        });
    });
    res.render('index', {
        locals: {
            page_title: 'Prices',
            price_dict: price_dict
        }
    });
});

Problem: Bei der layout engine ejs kommt ein leeres Array an, die Seite wird also schon ausgeliefert, obwohl kein Ergebnis vorhanden ist, weil die binance.prices()-Aufrufe nicht blockieren (ich weiß nicht, wie man das in JS nennt).

Auf dieser Seite https://www.sitepoint.com/delay-sleep-pause-wait/ habe ich Folgendes gefunden:

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function delayedGreeting() {
  console.log("Hello");
  await sleep(2000);
  console.log("World!");
}

delayedGreeting();
console.log("Goodbye!");

Das kann aber doch nicht die Lösung sein…

Wie macht man es richtig? Vielen Dank.

Willkommen in der Promise Hell.

Ja, doch, so läuft’s. Anstatt

customers = readCustomers();
balances = [];
foreach (customer in customers) {
    balanches.append(obtainBalance(customer));
}
foreach (balache in balances) {
    transfer(balance, myAccount);
}

muss mal halt sowas schreiben wie

readCustomers().andThen(customers => {
    foreach (customer in customers) {
        obtainBalance(customer).andThen(balance => {
            transfer(balance, myAccount);
        }
    }
}

aber das ist sch…lecht. Stattdessen sollte man Promise.all verwenden. Aber das ist schlecht. Stattdessen sollte man async/await verwenden. Aber das ist schlecht. Stattdessen sollte man Go oder Rust verwenden. Aber das ist schlecht.

Also pickt man sich seine beliebige Geschmachsrichtung von „schlecht“ heraus, und versucht, sich zu beruhigen mit „Das ist normal so“, „Das geht nicht anders“, „Das wird refactort, wenn ich mal Zeit habe und/oder eine neue JS-Funktionalität verfügbar ist“, „Das muss so sein“, „Das hat Gründe“, und natürlich das allseits beliebte „Andere machen das auch so“.

Hm. Ja. Keine wirklich hilfreiche Antwort.

Aber … ja, so läuft’s halt.

1 „Gefällt mir“

Hab (nach 5-6 Stunden) eine Lösung dafür… Alles andere als zufrieden, aber sie funktioniert:

app.get(bina_path, async (req, res) => {
    let price_dict = [];
    for (let i = 0; i < all_pairs.length; i++) {
        const name = all_pairs[i] + 'USDT';
        let price = await new Promise(resolve => {
            binance.prices(name, (error, ticker) => {
                resolve(ticker);
            });
        });
        console.log(price);
        price_dict.push({
            'name': name,
            'price': price[name]
        });
    }
    res.render('index', {
        locals: {
            page_title: 'Prices',
            price_dict: price_dict
        }
    });
});
  1. https://stackoverflow.com/a/37576787
  2. https://stackoverflow.com/a/37104225

let x = await new Promise(resolve => { sieht zwar furchtbar aus, wäre aber nicht so schlimm, wenn binance.prices(name, (error, ticker) => { nicht dasselbe tun würde wie binance.prices();:

app.get(bina_path, async (req, res) => {
    let price_dict = [];
    let prices = await binance.prices();
    for (let i = 0; i < all_pairs.length; i++) {
        const name = all_pairs[i] + 'USDT';
        price_dict.push({
            'name': name,
            'price': prices[name]
        });
    }
    res.render('index', {
        locals: {
            page_title: 'Prices',
            price_dict: price_dict
        }
    });
});

Sprich: Beides ist die gleiche Bulk-Funktion, nur filtert die erste die Ergebnisse. :face_vomiting:

Ok, Danke für den Hinweis mit der „Promise Hell“.

Edit: Also, die Funktion muss async sein, .forEach ist nicht mehr erlaubt, und man muss mit await auf das Ergebnis eines neuen Promises warten (oder man nutzt util.promisify: https://nodejs.org/dist/latest-v8.x/docs/api/util.html#util_util_promisify_original).

Wer hat sich das bloß ausgedacht?

Das sind die Seitenäste von „Das ist schlecht, stattdessen sollte man…“. Mit async/await kann man einigermaßen lesbaren code schreiben, und ich bin mir ziemlich sicher, dass man

        let price = await new Promise(resolve => {
            binance.prices(name, (error, ticker) => {
                resolve(ticker);
            });
        });

als

        let price = await binance.prices(name);

schreiben können sollte, aber … naja, vermutlich geht das in diesem Fall nicht, weil die API von dieser library in dieser Version noch bluebird-Promises verwendet und zu ES1956 kompatibel sein will, und das entsprechende polyfill, mit dem das alles funktionieren würde, nicht mit Node.js 10.4.3.5d kompatibel ist, weil man dafür unicode-Unterstützung in der gulpfile bräuchte, um den entsprechenden babel-step auszuführen, aber der Internet-Explorer 8.0 eben keine Tranparenz bei der box-shadow-property von CSS3 unterstützt.
Aber das sollte ja jedem klar sein.
Wir sind ja schließlich Informatiker.
Wir sind ja schließlich Informatiker, richtig?
:crazy_face:

Niemand. Das entsteht einfach so.

After being provoked by Quentin, Worth finally admits that he designed the maze’s outer shell—in the shape of a gigantic cube—for a shadowy bureaucracy, and guesses that its original purpose has been forgotten; they have been imprisoned within the maze simply to put it to use, otherwise it would only serve to be „pointless“.

Cube (1997 film) - Wikipedia

Ich für meinen Teil bin nur angehender Informatiker. :cry: Aber auch etwas „wissbegierig“. Zumindest so „wissbegierig“, dass ich nicht mehr gleich davonlaufe, wenn es leicht anfängt zu :nose: .

Ich bin noch etwas unbeholfen. @Marco13 weißt du vielleicht,

        let tick = await new Promise(resolve => {
            // Intervals: 1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,1M
            binance.candlesticks(name, "1m", (error, ticks, symbol) => {
                // console.info("candlesticks()", ticks);
                // let last_tick = ticks[ticks.length - 1];
                // let [time, open, high, low, close, volume, closeTime, assetVolume, trades, buyBaseVolume, buyAssetVolume, ignored] = last_tick;
                // console.info(symbol + " last close: " + close);
                resolve(ticks);
            }, { limit: 500 })
        });
        let inputVWAP = {
            open: [],
            high: [],
            low: [],
            close: [],
            volume: []
        };
        for (let t1 of tick) {
            inputVWAP.open.push(parseFloat(t1[1]));
            inputVWAP.high.push(parseFloat(t1[2]));
            inputVWAP.low.push(parseFloat(t1[3]));
            inputVWAP.close.push(parseFloat(t1[4]));
            inputVWAP.volume.push(parseFloat(t1[5]));
        }
        let vwap = new VWAP(inputVWAP).getResult();

wie ich hier das Promise auflösen könnte, und dann das angefragte Ergebnis (ticks) in die inputVWAP -„Struktur“ mappen könnte, ohne dabei eine Schleife zu verwenden? Das geht doch bestimmt alles ganz elegant…

Ich glaube, ich verwende die nodejs ECMAScript 2015 (ES6) Version…