Jak przekonwertować istniejący interfejs API wywołania zwrotnego na promises?
Chcę pracować z obietnicami, ale mam callback API w formacie takim jak:
1. Ładowanie DOM lub inne zdarzenie jednorazowe:
window.onload; // set to callback
...
window.onload = function() {
};
2. "Plain callback": {]}
function request(onChangeHandler) {
...
}
request(function() {
// change happened
...
});
3. Node style callback ("nodeback"):
function getStuff(dat, callback) {
...
}
getStuff("dataParam", function(err, data) {
...
})
4. Cała biblioteka z wywołaniami w stylu węzła:
API;
API.one(function(err, data) {
API.two(function(err, data2) {
API.three(function(err, data3) {
...
});
});
});
Jak pracować z API w promises, jak to "promisify"?
function request(onChangeHandler) {
...
}
request(function() {
// change happened
...
});
function getStuff(dat, callback) {
...
}
getStuff("dataParam", function(err, data) {
...
})
API;
API.one(function(err, data) {
API.two(function(err, data2) {
API.three(function(err, data3) {
...
});
});
});
18 answers
Obietnice mają stan, zaczynają się jako oczekujące i mogą się rozstrzygnąć:
- spełnione co oznacza, że obliczenia zakończyły się pomyślnie.
- odrzucono co oznacza, że obliczenia nie powiodły się.
Obietnica funkcji zwracających nigdy nie rzucaj, zamiast tego powinni zwrócić odmowę. Rzucanie z funkcji zwracającej obietnicę zmusi cię do użycia zarówno } catch {
i a .catch
. Osoby korzystające z promisified API nie oczekują obietnic rzucać. Jeśli nie jesteś pewien, jak działają asynchroniczne interfejsy API w JS - proszę zobacz najpierw tę odpowiedź.
1. Ładowanie DOM lub inne zdarzenie jednorazowe:
Tak więc, tworzenie obietnic zazwyczaj oznacza określenie, kiedy się rozliczają - to znaczy, kiedy przechodzą do fazy spełnionej lub odrzuconej, aby wskazać, że dane są dostępne (i mogą być dostępne za pomocą .then
).
Z nowoczesnymi implementacjami promise, które obsługują konstruktor Promise
jak natywny ES6 obietnice:
function load() {
return new Promise(function(resolve, reject) {
window.onload = resolve;
});
}
Następnie użyłbyś otrzymanej obietnicy w ten sposób:
load().then(function() {
// Do things after onload
});
Z bibliotekami, które obsługują deferred (użyjmy $q w tym przykładzie tutaj, ale użyjemy również jQuery później):
function load() {
var d = $q.defer();
window.onload = function() { d.resolve(); };
return d.promise;
}
W przeciwieństwie do jQuery, nie jest to możliwe.]}
function done() {
var d = $.Deferred();
$("#myObject").once("click",function() {
d.resolve();
});
return d.promise();
}
2. "Plain callback": {]}
[31]}te API są dość powszechne, ponieważ cóż ... wywołania zwrotne są powszechne w JS. Spójrzmy na wspólny przypadek posiadania onSuccess
i onFail
:
function getUserData(userId, onLoad, onFail) { …
function getUserData(userId, onLoad, onFail) { …
Z implementacje promise, które wspierają konstruktor Promise
, jak natywny ES6 promises:
function getUserDataAsync(userId) {
return new Promise(function(resolve, reject) {
getUserData(userId, resolve, reject);
});
}
Z bibliotekami, które obsługują deferred (użyjmy jQuery w tym przykładzie tutaj, ale użyliśmy również $q powyżej):
function getUserDataAsync(userId) {
var d = $.Deferred();
getUserData(userId, function(res){ d.resolve(res); }, function(err){ d.reject(err); });
return d.promise();
}
JQuery oferuje również formularz $.Deferred(fn)
, który ma tę zaletę, że pozwala nam napisać wyrażenie, które bardzo dokładnie emuluje formularz new Promise(fn)
, w następujący sposób:
function getUserDataAsync(userId) {
return $.Deferred(function(dfrd) {
getUserData(userId, dfrd.resolve, dfrd.reject);
}).promise();
}
Uwaga: tutaj wykorzystujemy fakt, że metody jQuery są resolve
i reject
są "odpinane"; tj. są one powiązane z instancją jQuery.Deferred (). Nie wszystkie biblioteki oferują tę funkcję.
3. Node style callback ("nodeback"):
Wywołania zwrotne w stylu węzła (nodeback) mają określony format, w którym wywołania zwrotne są zawsze ostatnim argumentem, a jego pierwszy parametr jest błędem. Najpierw promisify one ręcznie:
getStuff("dataParam", function(err, data) { …
Do:
function getStuffAsync(param) {
return new Promise(function(resolve, reject) {
getStuff(param, function(err, data) {
if (err !== null) reject(err);
else resolve(data);
});
});
}
Z deferreds można zrobić następujące (użyjmy Q dla tego przykładu, chociaż Q teraz obsługuje nową składnię , którą powinieneś preferować):
function getStuffAsync(param) {
var d = Q.defer();
getStuff(param, function(err, data) {
if (err !== null) d.reject(err);
else d.resolve(data);
});
return d.promise;
}
Ogólnie rzecz biorąc, nie powinieneś zbytnio obiecywać rzeczy ręcznie, większość bibliotek obietnic, które zostały zaprojektowane z myślą o Node, jak również obietnic natywnych w Node 8+ ma wbudowaną metodę obiecywania pakietów nodebacków. Na przykład
var getStuffAsync = Promise.promisify(getStuff); // Bluebird
var getStuffAsync = Q.denodeify(getStuff); // Q
var getStuffAsync = util.promisify(getStuff); // Native promises, node only
4. Cała biblioteka z wywołaniami w stylu węzła:
Nie ma tu złotej reguły, obiecujesz je jeden po drugim. Jednak niektóre implementacje obietnic pozwalają aby to zrobić zbiorczo, na przykład w Bluebird, konwersja API nodeback do API promise jest tak prosta, jak: {]}
Promise.promisifyAll(API);
Lub z rodzime obietnice w węzeł :
const { promisify } = require('util');
const promiseAPI = Object.entries(API).map(v => ({key, fn: promisify(v)}))
.reduce((o, p) => Object.assign(o, {[p.key]: p.fn}), {});
Uwagi:
- oczywiście, kiedy jesteś w
.then
opiekun nie musisz obiecywać rzeczy. Zwracanie obietnicy z obsługi.then
rozwiąże lub odrzuci z wartością tej obietnicy. Rzucanie z.then
jest również dobrą praktyką i odrzuci obietnicę - jest to słynna obietnica rzucania bezpieczeństwa. - W przypadku rzeczywistym
onload
należy użyćaddEventListener
zamiastonX
.
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-09-20 13:41:56
Dzisiaj mogę używać Promise
w Node.js
jako zwykłej metody Javascript.
Prosty i podstawowy przykład do Promise
(z KISS sposób):
Plain Kod asynchronicznego API Javascript:
function divisionAPI (number, divider, successCallback, errorCallback) {
if (divider == 0) {
return errorCallback( new Error("Division by zero") )
}
successCallback( number / divider )
}
Promise
Kod asynchronicznego API języka Javascript:
function divisionAPI (number, divider) {
return new Promise(function (fulfilled, rejected) {
if (divider == 0) {
return rejected( new Error("Division by zero") )
}
fulfilled( number / divider )
})
}
(polecam odwiedzenie tego pięknego źródła)
Również Promise
mogą być używane razem async\await
w ES7
, aby przepływ programu czekał na wynik fullfiled
, taki jak po:
function getName () {
return new Promise(function (fulfilled, rejected) {
var name = "John Doe";
// wait 3000 milliseconds before calling fulfilled() method
setTimeout (
function() {
fulfilled( name )
},
3000
)
})
}
async function foo () {
var name = await getName(); // awaits for a fulfilled result!
console.log(name); // the console writes "John Doe" after 3000 milliseconds
}
foo() // calling the foo() method to run the code
Inne użycie z tym samym kodem za pomocą metody .then()
function getName () {
return new Promise(function (fulfilled, rejected) {
var name = "John Doe";
// wait 3000 milliseconds before calling fulfilled() method
setTimeout (
function() {
fulfilled( name )
},
3000
)
})
}
// the console writes "John Doe" after 3000 milliseconds
getName().then(function(name){ console.log(name) })
Promise
może być również używany na dowolnej platformie, która jest oparta na Node.js jak react-native
.
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-03-10 07:23:17
Przed konwersją funkcji jako obietnicy w węźle.JS
var request = require('request'); //http wrapped module
function requestWrapper(url, callback) {
request.get(url, function (err, response) {
if (err) {
callback(err);
}else{
callback(null, response);
}
})
}
requestWrapper(url, function (err, response) {
console.log(err, response)
})
Po Przekształceniu
var request = require('request');
var Promise = require('bluebird');
function requestWrapper(url) {
return new Promise(function (resolve, reject) { //returning promise
request.get(url, function (err, response) {
if (err) {
reject(err); //promise reject
}else{
resolve(response); //promise resolve
}
})
})
}
requestWrapper('http://localhost:8080/promise_request/1').then(function(response){
console.log(response) //resolve callback(success)
}).catch(function(error){
console.log(error) //reject callback(failure)
})
Incase musisz obsłużyć wiele żądań
var allRequests = [];
allRequests.push(requestWrapper('http://localhost:8080/promise_request/1'))
allRequests.push(requestWrapper('http://localhost:8080/promise_request/2'))
allRequests.push(requestWrapper('http://localhost:8080/promise_request/5'))
Promise.all(allRequests).then(function (results) {
console.log(results);//result will be array which contains each promise response
}).catch(function (err) {
console.log(err)
});
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-06-12 21:34:27
Nie sądzę, aby window.onload
sugestia @Benjamin działała cały czas, ponieważ nie wykrywa, czy jest wywoływana po załadowaniu. Byłem przez to ugryziony wiele razy. Oto wersja, która powinna zawsze działać:
function promiseDOMready() {
return new Promise(function(resolve) {
if (document.readyState === "complete") return resolve();
document.addEventListener("DOMContentLoaded", resolve);
});
}
promiseDOMready().then(initOnLoad);
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-03-03 12:32:12
W release candidate dla Node.js 8.0.0, jest nowe narzędzie, util.promisify
(pisałem o util.promisify ), która zawiera zdolność do promisify dowolnej funkcji.
Nie różni się zbytnio od podejścia sugerowanego w innych odpowiedziach, ale ma tę zaletę, że jest podstawową metodą i nie wymaga dodatkowych zależności.
const fs = require('fs');
const util = require('util');
const readFile = util.promisify(fs.readFile);
Następnie masz readFile
metodę, która zwraca natywny Promise
.
readFile('./notes.txt')
.then(txt => console.log(txt))
.catch(...);
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-16 05:35:57
Możesz używać natywnych obietnic JavaScript z Node JS.
My Cloud 9 link kodowy: https://ide.c9.io/adx2803/native-promises-in-node
/**
* Created by dixit-lab on 20/6/16.
*/
var express = require('express');
var request = require('request'); //Simplified HTTP request client.
var app = express();
function promisify(url) {
return new Promise(function (resolve, reject) {
request.get(url, function (error, response, body) {
if (!error && response.statusCode == 200) {
resolve(body);
}
else {
reject(error);
}
})
});
}
//get all the albums of a user who have posted post 100
app.get('/listAlbums', function (req, res) {
//get the post with post id 100
promisify('http://jsonplaceholder.typicode.com/posts/100').then(function (result) {
var obj = JSON.parse(result);
return promisify('http://jsonplaceholder.typicode.com/users/' + obj.userId + '/albums')
})
.catch(function (e) {
console.log(e);
})
.then(function (result) {
res.end(result);
})
})
var server = app.listen(8081, function () {
var host = server.address().address
var port = server.address().port
console.log("Example app listening at http://%s:%s", host, port)
})
//run webservice on browser : http://localhost:8081/listAlbums
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-06-20 14:35:27
Węzeł.js 8.0.0 zawiera nowe Api util.promisify()
, które umożliwia standardowy węzeł.interfejsy API w stylu js callback mają być zawinięte w funkcję, która zwraca obietnicę. Przykładowe użycie util.promisify()
pokazano poniżej.
const fs = require('fs');
const util = require('util');
const readfile = util.promisify(fs.readFile);
readfile('/some/file')
.then((data) => { /** ... **/ })
.catch((err) => { /** ... **/ });
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-31 06:46:50
Biblioteka Q firmy kriskowal zawiera funkcje oddzwaniania do obietnicy. Metoda taka:
obj.prototype.dosomething(params, cb) {
...blah blah...
cb(error, results);
}
Można przekonwertować za pomocą Q. ninvoke
Q.ninvoke(obj,"dosomething",params).
then(function(results) {
});
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-04-07 18:30:14
Pod węzłem v7. 6+, który ma wbudowane obietnice i asynchronizację:
// promisify.js
let promisify = fn => (...args) =>
new Promise((resolve, reject) =>
fn(...args, (err, result) => {
if (err) return reject(err);
return resolve(result);
})
);
module.exports = promisify;
Jak używać:
let readdir = require('fs').readdir;
let promisify = require('./promisify');
let readdirP = promisify(readdir);
async function myAsyncFn(path) {
let entries = await readdirP(path);
return entries;
}
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-04-12 16:48:09
Gdy masz kilka funkcji, które biorą callback i chcesz, aby zamiast tego zwróciły obietnicę, możesz użyć tej funkcji do konwersji.
function callbackToPromise(func){
return function(){
// change this to use what ever promise lib you are using
// In this case i'm using angular $q that I exposed on a util module
var defered = util.$q.defer();
var cb = (val) => {
defered.resolve(val);
}
var args = Array.prototype.slice.call(arguments);
args.push(cb);
func.apply(this, args);
return defered.promise;
}
}
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-08-25 00:54:47
Ze zwykłym starym waniliowym javaScript, oto rozwiązanie do promisify API callback.
function get(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('get', url);
xhr.addEventListener('readystatechange', function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log('successful ... should call callback ... ');
callback(null, JSON.parse(xhr.responseText));
} else {
console.log('error ... callback with error data ... ');
callback(xhr, null);
}
}
});
xhr.send();
}
/**
* @function promisify: convert api based callbacks to promises
* @description takes in a factory function and promisifies it
* @params {function} input function to promisify
* @params {array} an array of inputs to the function to be promisified
* @return {function} promisified function
* */
function promisify(fn) {
return function () {
var args = Array.prototype.slice.call(arguments);
return new Promise(function(resolve, reject) {
fn.apply(null, args.concat(function (err, result) {
if (err) reject(err);
else resolve(result);
}));
});
}
}
var get_promisified = promisify(get);
var promise = get_promisified('some_url');
promise.then(function (data) {
// corresponds to the resolve function
console.log('successful operation: ', data);
}, function (error) {
console.log(error);
});
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-11-29 14:44:49
W Węźle.js 8 możesz promisify metody obiektowe w locie używając tego modułu npm:
Https://www.npmjs.com/package/doasync
Używa util.promisify i Proxy tak, aby twoje obiekty pozostały niezmienione. Memoizacja {[7] } odbywa się również za pomocą Weakmapów). Oto kilka przykładów:
Z obiektami:
const fs = require('fs');
const doAsync = require('doasync');
doAsync(fs).readFile('package.json', 'utf8')
.then(result => {
console.dir(JSON.parse(result), {colors: true});
});
Z FUNKCJAMI:
doAsync(request)('http://www.google.com')
.then(({body}) => {
console.log(body);
// ...
});
Możesz nawet użyć natywnych call
i apply
, aby związać niektóre kontekst:
doAsync(myFunc).apply(context, params)
.then(result => { /*...*/ });
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-10-12 22:19:07
Możesz użyć native Promise w ES6, do exemple radzenia sobie z setTimeout:
enqueue(data) {
const queue = this;
// returns the Promise
return new Promise(function (resolve, reject) {
setTimeout(()=> {
queue.source.push(data);
resolve(queue); //call native resolve when finish
}
, 10); // resolve() will be called in 10 ms
});
}
W tym przypadku obietnica nie ma powodu, aby zawieść, więc reject()
nigdy nie jest wywoływana.
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-27 13:00:54
Styl wywołania zwrotnego funkcja zawsze tak wygląda(prawie wszystkie funkcje w węźle.js to ten styl):
//fs.readdir(path[, options], callback)
fs.readdir('mypath',(err,files)=>console.log(files))
Ten styl ma tę samą cechę:
Funkcja callback jest przekazywana przez ostatni argument.
Funkcja callback zawsze akceptuje obiekt błędu jako pierwszy argument.
Więc możesz napisać funkcję do Konwersji Funkcji w takim stylu:
const R =require('ramda')
/**
* A convenient function for handle error in callback function.
* Accept two function res(resolve) and rej(reject) ,
* return a wrap function that accept a list arguments,
* the first argument as error, if error is null,
* the res function will call,else the rej function.
* @param {function} res the function which will call when no error throw
* @param {function} rej the function which will call when error occur
* @return {function} return a function that accept a list arguments,
* the first argument as error, if error is null, the res function
* will call,else the rej function
**/
const checkErr = (res, rej) => (err, ...data) => R.ifElse(
R.propEq('err', null),
R.compose(
res,
R.prop('data')
),
R.compose(
rej,
R.prop('err')
)
)({err, data})
/**
* wrap the callback style function to Promise style function,
* the callback style function must restrict by convention:
* 1. the function must put the callback function where the last of arguments,
* such as (arg1,arg2,arg3,arg...,callback)
* 2. the callback function must call as callback(err,arg1,arg2,arg...)
* @param {function} fun the callback style function to transform
* @return {function} return the new function that will return a Promise,
* while the origin function throw a error, the Promise will be Promise.reject(error),
* while the origin function work fine, the Promise will be Promise.resolve(args: array),
* the args is which callback function accept
* */
const toPromise = (fun) => (...args) => new Promise(
(res, rej) => R.apply(
fun,
R.append(
checkErr(res, rej),
args
)
)
)
Dla bardziej zwięzłego, powyższy przykład użyty ramda.js. Ramda.js jest doskonałą biblioteką do programowania funkcyjnego. W powyższym kodzie użyliśmy apply (jak javascript function.prototype.apply
) i append(jak javascript function.prototype.push
).
Możemy więc teraz przekonwertować funkcję stylu wywołania zwrotnego na funkcję stylu obietnicy:
const {readdir} = require('fs')
const readdirP = toPromise(readdir)
readdir(Path)
.then(
(files) => console.log(files),
(err) => console.log(err)
)
ToPromise i checkErr funkcja jest własna przez Berserk Biblioteka, jest biblioteką programowania funkcyjnego Fork przez ramda.js (create by me).
Mam nadzieję, że ta odpowiedź jest przydatna dla Ciebie.
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-30 13:46:20
es6-promisify
konwertuje funkcje oparte na wywołaniu zwrotnym do funkcji opartych na obietnicy.
const promisify = require('es6-promisify');
const promisedFn = promisify(callbackedFn, args);
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-10-17 23:56:27
Moja wersja funkcji callback
to P
Funkcja:
var P = function() {
var self = this;
var method = arguments[0];
var params = Array.prototype.slice.call(arguments, 1);
return new Promise((resolve, reject) => {
if (method && typeof(method) == 'function') {
params.push(function(err, state) {
if (!err) return resolve(state)
else return reject(err);
});
method.apply(self, params);
} else return reject(new Error('not a function'));
});
}
var callback = function(par, callback) {
var rnd = Math.floor(Math.random() * 2) + 1;
return rnd > 1 ? callback(null, par) : callback(new Error("trap"));
}
callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))
callback("callback", (err, state) => err ? console.error(err) : console.log(state))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
P(callback, "promise").then(v => console.log(v)).catch(e => console.error(e))
Funkcja P
wymaga, aby sygnatura wywołania zwrotnego była callback(error,result)
.
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-11-29 23:34:06
Możesz zrobić coś takiego
// @flow
const toPromise = (f: (any) => void) => {
return new Promise<any>((resolve, reject) => {
try {
f((result) => {
resolve(result)
})
} catch (e) {
reject(e)
}
})
}
export default toPromise
Następnie użyj go
async loadData() {
const friends = await toPromise(FriendsManager.loadFriends)
console.log(friends)
}
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-10-09 13:35:18
Możesz użyć pakietu callback2Promise npm do Konwersji Funkcji w stylu węzła na obietnice.`
var c2p = require('callback2promise');
// ordinary function with any number of parameters and a callback at the end
var nodeStyleFunc = function(param1, param2, callback){
setTimeout(
function(){
callback(null, 'done')
}, 200);
}
// convert the function to a promise
var promise = c2p(nodeStyleFunc)(param1, param2);
promise
.then(result => console.log(result))
.catch(err => console.log(err));
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-09-28 13:45:50