Jak uzyskać dostęp do poprzednich wyników obietnicy w łańcuchu. then ()?

Zrestrukturyzowałem mój kod na obietnice i zbudowałem wspaniały długi płaski łańcuch obietnic , składający się z wielu wywołań zwrotnych .then(). W końcu chcę zwrócić pewną wartość złożoną i muszę uzyskać dostęp do wielu pośrednich wyników obietnicy . Jednak wartości rozdzielczości od środka sekwencji nie są w zakresie w ostatnim wywołaniu zwrotnym, jak uzyskać do nich dostęp?

function getExample() {
    return promiseA(…).then(function(resultA) {
        // Some processing
        return promiseB(…);
    }).then(function(resultB) {
        // More processing
        return // How do I gain access to resultA here?
    });
}
Author: Peter Mortensen, 2015-01-31

15 answers

Break the chain

Kiedy musisz uzyskać dostęp do wartości pośrednich w łańcuchu, powinieneś podzielić łańcuch na te pojedyncze kawałki, których potrzebujesz. Zamiast dołączać jeden callback i jakoś próbuje używać jego parametr wiele razy, dołączyć wiele wywołań zwrotnych do tej samej obietnicy-wszędzie tam, gdzie potrzebujesz wartość wyniku. Nie zapominaj, że obietnica reprezentuje (proxy) przyszłą wartość! Obok wyprowadzania jednej obietnicy z drugiej w łańcuchu liniowym, użyj kombinatory obietnic, które są ci przekazywane przez bibliotekę, aby zbudować wartość wyniku.

Spowoduje to bardzo prosty przepływ sterowania, przejrzysty skład funkcjonalności i tym samym łatwą modularyzację.

function getExample() {
    var a = promiseA(…);
    var b = a.then(function(resultA) {
        // some processing
        return promiseB(…);
    });
    return Promise.all([a, b]).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

Zamiast destrukcji parametru w wywołaniu zwrotnym po Promise.all, które stało się dostępne tylko z ES6, w ES5 wywołanie then byłoby zastąpione przez sprytną metodę pomocniczą, która była dostarczana przez wiele bibliotek obietnic (Q, Bluebird , kiedy, ...): .spread(function(resultA, resultB) { ….

Bluebird posiada również dedykowany join function to replace that Promise.all+spread połączenie z prostszą (i bardziej efektywną) konstrukcją:

…
return Promise.join(a, b, function(resultA, resultB) { … });
 314
Author: Bergi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-30 08:14:59

ECMAScript Harmony

Oczywiście problem ten został również rozpoznany przez projektantów języka. Wykonali wiele pracy i propozycja funkcji async W końcu przekształciła się w

ECMAScript 8

Nie potrzebujesz już ani jednej then funkcji wywołania ani wywołania zwrotnego, ponieważ w funkcji asynchronicznej (która zwraca obietnicę podczas wywoływania) możesz po prostu poczekać, aż obietnice zostaną rozwiązane bezpośrednio. Posiada również dowolne struktury sterowania, takie jak warunki, pętle i klauzule try-catch, ale ze względu na wygodę nie potrzebujemy ich tutaj: {]}

async function getExample() {
    var resultA = await promiseA(…);
    // some processing
    var resultB = await promiseB(…);
    // more processing
    return // something using both resultA and resultB
}

ECMAScript 6

Kiedy czekaliśmy na ES8, używaliśmy już bardzo podobnej składni. ES6 został wyposażony w funkcje generatora , które pozwalają na rozbicie wykonania na kawałki na dowolnie umieszczone słowa kluczowe yield. Te plasterki mogą być uruchamiane po sobie, niezależnie, nawet asynchronicznie - i to właśnie robimy, gdy chcemy czekać na obiecaj rozwiązanie przed uruchomieniem następnego kroku.

Istnieją dedykowane biblioteki (jak co lub task.js ), ale także wiele bibliotek obietnic posiada funkcje pomocnicze (Q, Bluebird, kiedy ,...), które wykonują to asynchroniczne wykonanie krok po kroku dla ciebie, gdy dajesz im funkcję generatora, która daje obietnice.

var getExample = Promise.coroutine(function* () {
//               ^^^^^^^^^^^^^^^^^ Bluebird syntax
    var resultA = yield promiseA(…);
    // some processing
    var resultB = yield promiseB(…);
    // more processing
    return // something using both resultA and resultB
});

To zadziałało w Node.js od wersji 4.0 również kilka przeglądarek (lub ich wersji dev) obsługiwało składnia generatora stosunkowo wcześnie.

ECMAScript 5

Jednakże, jeśli chcesz / musisz być kompatybilny wstecz, nie możesz ich używać bez transpilera. Zarówno funkcje generatora, jak i funkcje asynchroniczne są obsługiwane przez obecne narzędzia, patrz na przykład dokumentacja Babel na generatorach i funkcje asynchroniczne .

I jest jeszcze wiele innych języków kompilacyjnych do JS które są dedykowane do ułatwiania programowania asynchronicznego. Oni zazwyczaj używają składni podobnej do await, (np. Iced CoffeeScript), ale są też inne, które mają Haskell-like do - notation (np. LatteJs, monadic, PureScript lub LispyScript ).

 197
Author: Bergi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-30 08:34:40

Kontrola Synchroniczna

Przypisanie wartości promises-for-later-needed-do zmiennych, a następnie uzyskanie ich wartości poprzez kontrolę synchroniczną. Przykład wykorzystuje metodę .value() bluebird, ale wiele bibliotek zapewnia podobną metodę.

function getExample() {
    var a = promiseA(…);

    return a.then(function() {
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // a is guaranteed to be fulfilled here so we can just retrieve its
        // value synchronously
        var aValue = a.value();
    });
}

To może być używane dla dowolnej liczby wartości:

function getExample() {
    var a = promiseA(…);

    var b = a.then(function() {
        return promiseB(…)
    });

    var c = b.then(function() {
        return promiseC(…);
    });

    var d = c.then(function() {
        return promiseD(…);
    });

    return d.then(function() {
        return a.value() + b.value() + c.value() + d.value();
    });
}
 88
Author: Esailija,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-31 13:16:31

Zagnieżdżanie (i) zamknięć

Używanie Closure ' ów do utrzymywania zakresu zmiennych (w naszym przypadku parametry funkcji success callback) jest naturalnym rozwiązaniem JavaScript. Z obietnicami możemy dowolnie zagnieżdżać i spłaszczać .then() wywołania zwrotne - są semantycznie równoważne, z wyjątkiem zakresu wewnętrznego.

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(function(resultB) {
            // more processing
            return // something using both resultA and resultB;
        });
    });
}

Oczywiście, to jest budowa piramidy wcięć. Jeśli wcięcia stają się zbyt duże, nadal można zastosować stare narzędzia, aby przeciwdziałać pyramid of doom : modularyzuj, używaj dodatkowych nazwanych funkcji i spłaszcz łańcuch obietnic, gdy tylko nie będziesz już potrzebować zmiennej.
W teorii zawsze można uniknąć więcej niż dwóch poziomów zagnieżdżania (poprzez jednoznaczne zamknięcie wszystkich zamknięć), w praktyce używać ich tyle, ile jest rozsądnych.

function getExample() {
    // preprocessing
    return promiseA(…).then(makeAhandler(…));
}
function makeAhandler(…)
    return function(resultA) {
        // some processing
        return promiseB(…).then(makeBhandler(resultA, …));
    };
}
function makeBhandler(resultA, …) {
    return function(resultB) {
        // more processing
        return // anything that uses the variables in scope
    };
}

Możesz również używać funkcji pomocniczych dla tego rodzaju częściowej aplikacji , Jak _.partialz podkreślenia/lodash lub metoda native .bind() , aby dalsze zmniejszenie wcięcia:

function getExample() {
    // preprocessing
    return promiseA(…).then(handlerA);
}
function handlerA(resultA) {
    // some processing
    return promiseB(…).then(handlerB.bind(null, resultA));
}
function handlerB(resultA, resultB) {
    // more processing
    return // anything that uses resultA and resultB
}
 50
Author: Bergi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:25

Explicit pass-through

Podobnie jak zagnieżdżanie wywołań zwrotnych, technika ta opiera się na zamknięciach. Jednak łańcuch pozostaje płaski - zamiast przekazywać tylko ostatni wynik, jakiś obiekt stanu jest przekazywany dla każdego kroku. Te obiekty stanu gromadzą wyniki poprzednich działań, przekazując wszystkie wartości, które będą potrzebne później, oraz wynik bieżącego zadania.

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(b => [resultA, b]); // function(b) { return [resultA, b] }
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

Tutaj, ta mała strzałka {[4] } jest funkcją, która zamyka się nad resultA i przekazuje tablicę obu wyników do następnego kroku. Który używa składni destrukcji parametrów, aby ponownie podzielić ją na pojedyncze zmienne.

Zanim destrukcja stała się dostępna z ES6, sprytna metoda pomocnicza o nazwie .spread() była dostarczana przez wiele bibliotek promise ( Q, Bluebird, kiedy ,...). Przyjmuje funkcję z wieloma parametrami - po jednym dla każdego elementu tablicy-do użycia jako .spread(function(resultA, resultB) { ….

Oczywiście, że zamknięcie potrzebne tutaj może być jeszcze bardziej uproszczone przez niektórych funkcje pomocnicze, np.

function addTo(x) {
    // imagine complex `arguments` fiddling or anything that helps usability
    // but you get the idea with this simple one:
    return res => [x, res];
}

…
return promiseB(…).then(addTo(resultA));

Alternatywnie, możesz użyć Promise.all, aby przedstawić obietnicę dla tablicy:

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return Promise.all([resultA, promiseB(…)]); // resultA will implicitly be wrapped
                                                    // as if passed to Promise.resolve()
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

I możesz używać nie tylko tablic, ale arbitralnie złożonych obiektów. Na przykład z _.extend lub Object.assign w innej funkcji pomocniczej:

function augment(obj, name) {
    return function (res) { var r = Object.assign({}, obj); r[name] = res; return r; };
}

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(augment({resultA}, "resultB"));
    }).then(function(obj) {
        // more processing
        return // something using both obj.resultA and obj.resultB
    });
}

Podczas gdy ten wzór gwarantuje płaski łańcuch i jawne obiekty stanu mogą poprawić przejrzystość, będzie to uciążliwe dla długiego łańcucha. Zwłaszcza, gdy potrzebujesz tylko państwa sporadycznie, nadal trzeba przejść przez każdy krok. Dzięki temu stałemu interfejsowi pojedyncze wywołania w łańcuchu są raczej szczelnie sprzężone i nieelastyczne do zmiany. To sprawia, że faktoring pojedynczych kroków trudniejsze, a wywołań zwrotnych nie mogą być dostarczane bezpośrednio z innych modułów - zawsze muszą być owinięte w kod kotła, który dba o stan. Abstrakcyjne funkcje pomocnicze, takie jak powyższe, mogą nieco złagodzić ból, ale zawsze będą obecne.

 44
Author: Bergi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-25 17:26:07

Zmienny stan kontekstowy

Trywialnym (ale nieeleganckim i raczej błędnym) rozwiązaniem jest użycie zmiennych o wyższym zakresie (do których mają dostęp wszystkie wywołania zwrotne w łańcuchu) i zapisanie do nich wartości wyniku, gdy je otrzymasz:

function getExample() {
    var resultA;
    return promiseA(…).then(function(_resultA) {
        resultA = _resultA;
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // more processing
        return // something using both resultA and resultB
    });
}

Zamiast wielu zmiennych można również użyć (początkowo pustego) obiektu, na którym wyniki są zapisywane jako dynamicznie tworzone właściwości.

To rozwiązanie ma kilka wad:

  • Stan zmienny jest brzydkie i zmienne globalne są złe .
  • ten wzorzec nie działa poza granicami funkcji, modularyzacja funkcji jest trudniejsza, ponieważ ich deklaracje nie mogą opuszczać wspólnego zakresu
  • zakres zmiennych nie uniemożliwia dostępu do nich przed ich zainicjowaniem. Jest to szczególnie prawdopodobne w przypadku złożonych konstrukcji obietnic (pętle, rozgałęzienia, wyrostki), w których mogą wystąpić warunki wyścigu. Przekazując stan jawnie, a deklaratywny projekt to obiecuje zachęcić, wymusza czystszy styl kodowania, który może temu zapobiec.
  • trzeba poprawnie wybrać zakres dla tych współdzielonych zmiennych. Musi być lokalny dla wykonywanej funkcji, aby zapobiec Warunkom wyścigu pomiędzy wieloma równoległymi wywołaniami, tak jak miałoby to miejsce, gdyby na przykład stan był przechowywany na instancji.

Biblioteka Bluebird zachęca do użycia obiektu, który jest przekazywany, używając ich metody bind() do przypisania obiektu kontekstowego do łańcuch obietnic. Będzie on dostępny z każdej funkcji zwrotnej za pośrednictwem w inny sposób bezużyteczny this słowo kluczowe . Chociaż właściwości obiektu są bardziej podatne na niewykryte literówki niż zmienne, wzór jest dość sprytny:

function getExample() {
    return promiseA(…)
    .bind({}) // Bluebird only!
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }).bind(); // don't forget to unbind the object if you don't want the
               // caller to access it
}

Takie podejście można łatwo symulować w bibliotekach promise, które nie obsługują .bind (choć w nieco bardziej wyrazisty sposób i nie może być użyty w wyrażeniu):

function getExample() {
    var ctx = {};
    return promiseA(…)
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }.bind(ctx)).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }.bind(ctx));
}
 28
Author: Bergi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:47

Node 7.4 obsługuje teraz połączenia asynchroniczne / oczekujące z flagą harmony.

Spróbuj tego:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

I uruchom plik z:

node --harmony-async-await getExample.js

Proste jak można!

 5
Author: Antoine,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-21 22:14:57

Kolejna odpowiedź, używając babel-node w wersji

Using async - await

npm install -g [email protected]

example.js:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

Potem biegnij babel-node example.js i voila!

 4
Author: Antoine,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-21 19:01:02

Mniej surowy spin na "Mutable contextual state"

Użycie obiektu o zasięgu lokalnym do zbierania pośrednich wyników w łańcuchu obietnic jest rozsądnym podejściem do postawionego pytania. Rozważ następujący fragment:

function getExample(){
    //locally scoped
    const results = {};
    return promiseA(...).then(function(resultA){
        results.a = resultA;
        return promiseB(...);
    }).then(function(resultB){
        results.b = resultB;
        return promiseC(...);
    }).then(function(resultC){
        //Resolve with composite of all promises
        return Promise.resolve(results.a + results.b + resultC);
    }).catch(function(error){
        return Promise.reject(error);
    });
}
  • zmienne globalne są złe, więc to rozwiązanie wykorzystuje zmienną o zasięgu lokalnym, która nie powoduje szkody. Jest on dostępny tylko w ramach funkcji.
  • Mutable state jest brzydki, ale to nie mutuje stanu w brzydki sposób. Brzydula mutable state tradycyjnie odnosi się do modyfikowania stanu argumentów funkcji lub zmiennych globalnych, ale to podejście po prostu modyfikuje stan zmiennej o zasięgu lokalnym, która istnieje wyłącznie w celu agregacji wyników obietnicy...zmienna, która umrze zwykłą śmiercią, gdy obietnica się rozwiąże.
  • obietnice pośrednie nie mają dostępu do stanu obiektu results, ale nie wprowadza to jakiegoś strasznego scenariusza, w którym jedna z obietnic w łańcuchu będzie zbuntuj się i sabotuj swoje wyniki. Odpowiedzialność za ustawienie wartości w każdym kroku obietnicy ogranicza się do tej funkcji, a ogólny wynik będzie albo poprawny, albo incorrect...it nie będzie to jakiś błąd, który pojawi się po latach w produkcji(chyba, że zamierzasz!)
  • nie wprowadza to scenariusza warunku rasy, który powstałby w wyniku równoległego wywołania, ponieważ nowa instancja zmiennej results jest tworzona dla każdego wywołania getExample funkcja.
 3
Author: Jay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-25 12:04:12

Inna odpowiedź, używając sekwencyjnego executora nsynjs :

function getExample(){

  var response1 = returnPromise1().data;

  // promise1 is resolved at this point, '.data' has the result from resolve(result)

  var response2 = returnPromise2().data;

  // promise2 is resolved at this point, '.data' has the result from resolve(result)

  console.log(response, response2);

}

nynjs.run(getExample,{},function(){
    console.log('all done');
})

Update: dodano przykład roboczy

function synchronousCode() {
     var urls=[
         "https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js",
         "https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js",
         "https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"
     ];
     for(var i=0; i<urls.length; i++) {
         var len=window.fetch(urls[i]).data.text().data.length;
         //             ^                   ^
         //             |                   +- 2-nd promise result
         //             |                      assigned to 'data'
         //             |
         //             +-- 1-st promise result assigned to 'data'
         //
         console.log('URL #'+i+' : '+urls[i]+", length: "+len);
     }
}

nsynjs.run(synchronousCode,{},function(){
    console.log('all done');
})
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
 2
Author: amaksr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-13 14:08:03

W tych dniach również spotykam się z takimi pytaniami jak ty. W końcu znalazłem dobre rozwiązanie z pytaniem, jest proste i dobre do czytania. Mam nadzieję, że to ci pomoże.

Zgodnie z how-to-chain-javascript-promises

Ok, spójrzmy na kod:

const firstPromise = () => {
    const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('first promise is completed');
            resolve({data: '123'});
        }, 2000);
    });
};

const secondPromise = (someStuff) => {
    const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('second promise is completed');
            resolve({newData: `${someStuff.data} some more data`});
        }, 2000);
    });
};

const thirdPromise = (someStuff) => {
    const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('third promise is completed');
            resolve({result: someStuff});
        }, 2000);
    });
};

firstPromise()
    .then(seondPromise)
    .then(thirdPromise)
    .then(data => {
        console.log(data);
    });
 2
Author: yzfdjzwl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-25 06:34:25

Nie zamierzam używać tego wzorca w moim własnym kodzie, ponieważ nie jestem wielkim fanem używania zmiennych globalnych. Jednak w szczypcie to zadziała.

Użytkownik jest obiecanym modelem mangusty.

var globalVar = '';

User.findAsync({}).then(function(users){
  globalVar = users;
}).then(function(){
  console.log(globalVar);
});
 1
Author: Antoine,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-11 19:01:01

Używając bluebird, możesz użyć metody .bind do współdzielenia zmiennych w łańcuchu obietnic:

somethingAsync().bind({})
.spread(function (aValue, bValue) {
    this.aValue = aValue;
    this.bValue = bValue;
    return somethingElseAsync(aValue, bValue);
})
.then(function (cValue) {
    return this.aValue + this.bValue + cValue;
});

Proszę sprawdzić ten link, aby uzyskać więcej informacji:

Http://bluebirdjs.com/docs/api/promise.bind.html

 1
Author: alphakevin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-14 11:11:36
function getExample() {
    var retA, retB;
    return promiseA(…).then(function(resultA) {
        retA = resultA;
        // Some processing
        return promiseB(…);
    }).then(function(resultB) {
        // More processing
        //retA is value of promiseA
        return // How do I gain access to resultA here?
    });
}

Easy way: D

 1
Author: Minh Giang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-03 09:45:55

Myślę, że możesz użyć hash RSVP.

Coś jak poniżej:

    const mainPromise = () => {
        const promise1 = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('first promise is completed');
                resolve({data: '123'});
            }, 2000);
        });

        const promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('second promise is completed');
                resolve({data: '456'});
            }, 2000);
        });

        return new RSVP.hash({
              prom1: promise1,
              prom2: promise2
          });

    };


   mainPromise()
    .then(data => {
        console.log(data.prom1);
        console.log(data.prom2);
    });
 1
Author: Vishu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-29 10:34:10