This question already has an answer here:
Asynchronous operations in constructor
(1 answer)
Closed 4 years ago.
I am working on a project in Node - a language with which I have little familiarity.
In the project, I have a class that will be responsible for reading and writing data to a database - in this case, LevelDB. Ideally, I'd like to set-up the database connection in the constructor synchronously so that methods (writeItem, readItem, etc.) don't fail if they're called too fast. Or in other words, I don't want the constructor to return and allow the next line of code to run until all the promises are fulfilled.
I think I am either missing something fundamental to the language or there is some design pattern in node that I'm not aware of. A toy example that fails in the same way is here:
class AClass {
constructor(n) {
this.name = n;
console.log('calling init.');
this.init();
console.log('init returned.');
}
func() {
return new Promise(resolve => {
setTimeout(() => {
resolve(true);
}, 2000);
});
}
async init() {
console.log('calling func()');
let x = await this.func();
console.log('after func(): ');
}
}
let x = new AClass('test');
console.log(JSON.stringify(x));
This produces the output:
calling init.
calling func()
init returned.
{"name":"test"}
after func():
This is surprising to me. I would have expected:
calling init.
calling func()
after func():
init returned.
{"name":"test"}
The final objective is to instantiate a class that connects to a levelDB instance and does not return the object until that connection is made. So the code might look something like this:
let storage = new StorageObject();
storage.addItem(key, value); // <-- this fails unless StorageObject
// finishes the db setup.
Thanks!
Sam
Your objective of not returning the instance until after the connection won't work (at least not like this). The constructor's job is to create an instance and return that. Functions need to return something synchronously. If it's performing some async operation then a function can return a promise instead, but you don't want a promise from the constructor — you need the instance.
The easy way to do this is to require your object to be initialized after it's created, then the constructor can construct and return the instance and the init function is free to return a promise:
class AClass {
constructor(n) {/* some setup */}
func() {
return new Promise(resolve => {
setTimeout(() => {
resolve("some name");
}, 1000);
});
}
async init() {
this.name = await this.func();
return this
}
}
new AClass('test').init()
.then((initialized_obj) => console.log(initialized_obj))
If you're doing this in node, you could also use eventEmitters to emit an event when the instance has been initialized. Something like:
const EventEmitter = require('events');
class AClass extends EventEmitter{
constructor(n) {
super()
this.init()
.then(() => this.emit("initialized"))
}
func() {
return new Promise(resolve => {
setTimeout(() => {
resolve("some name");
}, 1000);
});
}
async init() {
this.name = await this.func();
return this
}
}
let x = new AClass('test')
x.on('initialized', () => console.log(x.name))
Two things about your code:
When you call an async function (this.init()), it will be executed uptill its returned await statement and will return a promise and the control will go to next line (console.log('init returned.')). Understanding this will resolve your confusion.
Code after await statement (console.log('after func(): ');) will execute only after awaited promise has been resolved.
I have repurposed your code to do what you want.
async function AClass(n) {
let obj = {}
obj.func = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve(true);
}, 2000);
});
};
obj.init = async function () {
console.log('calling func()');
let x = await obj.func();
console.log('after func(): ');
};
obj.name = n;
console.log('calling init.');
await obj.init();
console.log('init returned.');
return obj;
}
let x = AClass('test');
x.then((resolveValue) => {
/*
*Now the "class" has been instantiated,
* code to use the object of the "class" goes here
* */
console.log(JSON.stringify(resolveValue));
});
If you really want the constructor to return a working AClass synchronously, you can do it by rewriting other parts of code a little. I'm assuming that the methods writeItem, readItem, etc are asynchronous. All you have to do is rewrite these methods so that they wait for the object to be initialised (if necessary) before proceeding.
For example, suppose your class looks like this:
class AClass {
constructor(n) {
this.name = n;
this.init();
}
async init() {
...
}
async writeItem(item) {
return await db.writeItem(item);
}
async readItem(itemId) {
return await db.readItem(itemId);
}
}
You should then be able to rewrite it as follows:
class AClass {
constructor(n) {
this.name = n;
this.awaitInit = this.init();
}
async init() {
...
}
async writeItem(item) {
await this.awaitInit;
return await db.writeItem(item);
}
async readItem(itemId) {
await this.awaitInit;
return await db.readItem(itemId);
}
}
Related
I want to test a class, which contains a private function (Bar), which is called from setInterval. This private function is async and requires a sleep function.
Class I want to test
export class Foo
{
constructor ()
{
setInterval(async () => await this.Bar(), 1000);
}
private async Bar()
{
this.A();
await this.sleep(5000);
this.B();
}
private A()
{
console.log("A");
}
private B()
{
console.log("B");
}
public async sleep(ms: number)
{
return new Promise(resolve => setTimeout(resolve, ms));
}
}
Mocha test
it.only("test", () =>
{
let cut = new Foo();
let ASpy = sinon.spy(cut, 'A');
let BSpy = sinon.spy(cut, 'B');
sinon.stub(cut, 'sleep').resolves();
fakeClock.tick(1000);
ASpy.should.be.calledOnce;
BSpy.should.be.calledOnce;
ASpy.restore();
BSpy.restore();
});
The test above fails, because function B is called too late.
Result is:
A
1) test
B
When I modify the code, that it doesn't use the setIntervall it works.
Any hint how to get this test work?
Thanks in advance.
I found a "dirty" solution for my issue. Following test works as I want:
it.only("test", (done) =>
{
let cut = new Foo();
let ASpy = sinon.spy(cut, 'A');
let BSpy = sinon.spy(cut, 'B');
sinon.stub(cut, 'sleep').resolves();
fakeClock.tick(1000);
fakeClock.restore(); // restore timer so that following setTimeout works
setTimeout(() =>
{
ASpy.should.be.calledOnce;
BSpy.should.be.calledOnce;
ASpy.restore();
BSpy.restore();
done();
}, 0);
});
class Calc {
constructor(num) {
this.num = num;
}
add() {
// code
}
subtract() {
// code
}
multiply() {
// code
}
divide() {
// code
}
}
const getRes = async () => {
const res = await new Calc(10)
.add(30)
.subtract(5)
.multiply(2);
console.log(res) //prints the result
};
getRes();
How do i achieve this behaviour? I want to be able to chain all the methods (which in this example are add, subtract, multiply, divide) one after another and when i await them it should return the result same as when we await mongoose queries.
I know ordinary calculation isn't asynchronous, but imagine that the methods were asynchronous - what would the proper code to achieve the desired effect look like?
You can return an object which has the add, subtract, etc methods on it. When those methods are invoked, reassign an internal property of the instance, which holds the Promise. At the end of the chain, access that Promise property on the instance:
class Calc {
constructor(num) {
this.prom = Promise.resolve(num);
}
add(arg) {
this.prom = this.prom.then(res => res + arg);
return this;
}
subtract(arg) {
this.prom = this.prom.then(res => res - arg);
return this;
}
multiply(arg) {
this.prom = this.prom.then(res => res * arg);
return this;
}
}
const getRes = async () => {
const res = await new Calc(10)
.add(30)
.subtract(5)
.multiply(2)
.prom;
console.log(res) //prints the result
};
getRes();
Sometimes code would like to know if a particular function (or children) are running or not. For instance, node.js has domains which works for async stuff as well (not sure if this includes async functions).
Some simple code to explain what I need would by like this:
inUpdate = true;
try {
doUpdate();
} finally {
inUpdate = false;
}
This could then be used something like:
function modifyThings() {
if (inUpdate) throw new Error("Can't modify while updating");
}
With the advent of async this code breaks if the doUpdate() function is asynchronous. This was of course already true using callback-style functions.
The doUpdate function could of course be patched to maintain the variable around every await, but even if you have control over the code, this is cumbersome and error prone and this breaks when trying to track async function calls inside doUpdate.
I tried monkey-patching Promise.prototype:
const origThen = Promise.prototype.then;
Promise.prototype.then = function(resolve, reject) {
const isInUpdate = inUpdate;
origThen.call(this, function myResolve(value) {
inUpdate = isInUpdate;
try {
return resolve(value);
} finally {
inUpdate = false;
}
}, reject);
}
Unfortunately this doesn't work. I'm not sure why, but the async continuation code ends up running outside of the resolve call stack (probably using a microtask).
Note that it's not enough to simply do:
function runUpdate(doUpdate) {
inUpdate = true;
doUpdate.then(() => inUpdate = false).catch(() => inUpdate = false);
}
The reason is:
runUpdate(longAsyncFunction);
console.log(inUpdate); // incorrectly returns true
Is there any way to track something from outside an async function so it's possible to tell if the function called, or any of its descendant calls are running?
I know that it's possible to simulate async functions with generators and yield, in which case we have control over the call stack (since we can call gen.next()) but this is a kludge which the advent of async functions just got around to solving, so I'm specifically looking for a solution that works with native (not Babel-generated) async functions.
Edit: To clarify the question: Is there's a way for outside code to know if a particular invocation of an async function is running or if it is suspended, assuming that this code is the caller of the async function. Whether it's running or not would be determined by a function that ultimately is called by the async function (somewhere in the stack).
Edit: To clarify some more: The intended functionality would be the same as domains in node.js, but also for the browser. Domains already work with Promises, so async functions probably work as well (not tested).
This code allows me to do what I want to a certain extent:
function installAsyncTrack() {
/* global Promise: true */
if (Promise.isAsyncTracker) throw new Error('Only one tracker can be installed');
const RootPromise = Promise.isAsyncTracker ? Promise.rootPromise : Promise;
let active = true;
const tracker = {
track(f, o, ...args) {
const prevObj = tracker.trackObj;
tracker.trackObj = o;
try {
return f.apply(this, args);
} finally {
tracker.trackObj = prevObj;
}
},
trackObj: undefined,
uninstall() {
active = false;
if (Promise === AsyncTrackPromise.prevPromise) return;
if (Promise !== AsyncTrackPromise) return;
Promise = AsyncTrackPromise.prevPromise;
}
};
AsyncTrackPromise.prototype = Object.create(Promise);
AsyncTrackPromise.rootPromise = RootPromise;
AsyncTrackPromise.prevPromise = Promise;
Promise = AsyncTrackPromise;
AsyncTrackPromise.resolve = value => {
return new AsyncTrackPromise(resolve => resolve(value));
};
AsyncTrackPromise.reject = val => {
return new AsyncTrackPromise((resolve, reject) => reject(value));
};
AsyncTrackPromise.all = iterable => {
const promises = Array.from(iterable);
if (!promises.length) return AsyncTrackPromise.resolve();
return new AsyncTrackPromise((resolve, reject) => {
let rejected = false;
let results = new Array(promises.length);
let done = 0;
const allPromises = promises.map(promise => {
if (promise && typeof promise.then === 'function') {
return promise;
}
return new AsyncTrackPromise.resolve(promise);
});
allPromises.forEach((promise, ix) => {
promise.then(value => {
if (rejected) return;
results[ix] = value;
done++;
if (done === results.length) {
resolve(results);
}
}, reason => {
if (rejected) return;
rejected = true;
reject(reason);
});
});
});
};
AsyncTrackPromise.race = iterable => {
const promises = Array.from(iterable);
if (!promises.length) return new AsyncTrackPromise(() => {});
return new AsyncTrackPromise((resolve, reject) => {
let resolved = false;
if (promises.some(promise => {
if (!promise || typeof promise.then !== 'function') {
resolve(promise);
return true;
}
})) return;
promises.forEach((promise, ix) => {
promise.then(value => {
if (resolved) return;
resolved = true;
resolve(value);
}, reason => {
if (resolved) return;
resolved = true;
reject(reason);
});
});
});
};
function AsyncTrackPromise(handler) {
const promise = new RootPromise(handler);
promise.trackObj = tracker.trackObj;
promise.origThen = promise.then;
promise.then = thenOverride;
promise.origCatch = promise.catch;
promise.catch = catchOverride;
if (promise.finally) {
promise.origFinally = promise.finally;
promise.finally = finallyOverride;
}
return promise;
}
AsyncTrackPromise.isAsyncTracker = true;
function thenOverride(resolve, reject) {
const trackObj = this.trackObj;
if (!active || trackObj === undefined) return this.origThen.apply(this, arguments);
return this.origThen.call(
this,
myResolver(trackObj, resolve),
reject && myResolver(trackObj, reject)
);
}
function catchOverride(reject) {
const trackObj = this.trackObj;
if (!active || trackObj === undefined) return this.origCatch.catch.apply(this, arguments);
return this.origCatch.call(
this,
myResolver(trackObj, reject)
);
}
function finallyOverride(callback) {
const trackObj = this.trackObj;
if (!active || trackObj === undefined) return this.origCatch.catch.apply(this, arguments);
return this.origCatch.call(
this,
myResolver(trackObj, reject)
);
}
return tracker;
function myResolver(trackObj, resolve) {
return function myResolve(val) {
if (trackObj === undefined) {
return resolve(val);
}
RootPromise.resolve().then(() => {
const prevObj = tracker.trackObj;
tracker.trackObj = trackObj;
RootPromise.resolve().then(() => {
tracker.trackObj = prevObj;
});
});
const prevObj = tracker.trackObj;
tracker.trackObj = trackObj;
try {
return resolve(val);
} finally {
tracker.trackObj = prevObj;
}
};
}
}
tracker = installAsyncTrack();
function track(func, value, ...args) {
return tracker.track(func, { value }, value, ...args);
}
function show(where, which) {
console.log('At call', where, 'from', which, 'the value is: ', tracker.trackObj && tracker.trackObj.value);
}
async function test(which, sub) {
show(1, which);
await delay(Math.random() * 100);
show(2, which);
if (sub === 'resolve') {
await Promise.resolve(test('sub'));
show(3, which);
}
if (sub === 'call') {
await test(which + ' sub');
show(3, which);
}
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
track(test, 'test1');
track(test, 'test2');
track(test, 'test3', 'resolve');
track(test, 'test4', 'call');
It replaces the native Promise with my own. This promise stores the current context (taskObj) on the promise.
When the .then callback or its ilk are called, it does the following:
It creates a new native promise that immediately resolves. This adds a new microtask to the queue (according to spec, so should be reliable).
It calls the original resolve or reject. At least in Chrome and Firefox, this generates another microtask onto the queue that will run next part of the async function. Not sure what the spec has to say about this yet. It also restores the context around the call so that if it's not await that uses it, no microtask gets added here.
The first microtask gets executed, which is my first (native) promise being resolved. This code restores the current context (taskObj). It also creates a new resolved promise that queues another microtask
The second microtask (if any) gets executed, running the JS in the async function to until it hits the next await or returns.
The microtask queued by the first microtask gets executed, which restores the context to what it was before the Promise resolved/rejected (should always be undefined, unless set outside a tracker.track(...) call).
If the intercepted promise is not native (e.g. bluebird), it still works because it restores the state during the resolve(...) (and ilk) call.
There's one situation which I can't seem to find a solution for:
tracker.track(async () => {
console.log(tracker.taskObj); // 'test'
await (async () => {})(); //This breaks because the promise generated is native
console.log(tracker.taskObj); // undefined
}, 'test')
A workaround is to wrap the promise in Promise.resolve():
tracker.track(async () => {
console.log(tracker.taskObj); // 'test'
await Promise.resolve((async () => {})());
console.log(tracker.taskObj); // undefined
}, 'test')
Obviously, a lot of testing for all the different environments is needed and the fact that a workaround for sub-calls is needed is painful. Also, all Promises used need to either be wrapped in Promise.resolve() or use the global Promise.
[is it] possible to tell if the function called, or any of its descendant calls are running?
Yes. The answer is always no. Cause there is only one piece of code running at a time. Javascript is single threaded per definition.
Don't make it any more complicated than it needs to be. If doUpdate returns a promise (like when it is an async function), just wait for that:
inUpdate = true;
try {
await doUpdate();
//^^^^^
} finally {
inUpdate = false;
}
You can also use the finally Promise method:
var inUpdate = true;
doUpdate().finally(() => {
inUpdate = false;
});
That'll do just like like your synchronous code, having inUpdate == true while the function call or any of its descendants are running. Of course that only works if the asynchronous function doesn't settle the promise before it is finished doing its thing. And if you feel like the inUpdate flag should only be set during some specific parts of the doUpdate function, then yes the function will need to maintain the flag itself - just like it is the case with synchronous code.
When designing OOP ES 5-6, where I combine prototypes with ES 6 functions. Everything is running in ElectronJS, I do not want a new version that fully supports ES 7 too, so for example the definition of import {trida} from "./cesta" must be solved require ('./ path'). My problem, but it's in Promise.
If I want to program object, I want every object to do what it has and pass the result to the main branch of the program, which contains the column of the whole procedure - object list - Content. If fun enter setTimeout (), you need to use the Promise here, which waits until the function is executed and continues to call another object.
let importing = function(){
this.name = "boot";
}
importing.prototype.start = function(resolve){
this.resolve = resolve;
setTimeout(this.test.bind(this),1000);
console.log('start -------->');
}
importing.prototype.test = function(){
this.resolve('Test');
console.log('Test -------->');
}
importing.prototype.end = function(resolve){
console.log('end -------->');
this.resolve = resolve;
this.resolve('end');
}
let geko;
let scan = new importing();
Promise.resolve(geko)
.then((geko) => new Promise(scan.start.bind(scan)))
.then((geko) => new Promise(scan.end.bind(scan)))
.catch(geko => {
console.log('Error message: ',geko)
})
Here is the problem, I do not want the features to nest in the prototype functions, I want to call every object, nicely in order, clearly. Like any book, it has its Chapter Content, and then the chapters itself and I want to have a quick entry into what I have programmed and did not look at how much a mouse was taken after a week. But for this operation, besides Promise, I also have to use the bind () function because:
importing.prototype.start = function(){
// here this acts as a window object not as importing
// why I have this object called scan.start.bind (scan)
// and here again in setTimeout i have to bind (this) to get it in this.test
// could access this importing - scan object
setTimeout(this.test.bind(this),300);
}
You would find a better way??
You should not pass methods as the argument to the new Promise constructor. If the method is asynchronous, it should return a promise by itself; if it's not asynchronous then you shouldn't be using any promises.
function importing(){
this.name = "boot";
}
importing.prototype.start = function() {
console.log('start -------->');
return new Promise(resolve => {
setTimeout(resolve, 1000); // put the native async call in here, and nothing else!
}).then(() =>
this.test()
);
};
importing.prototype.test = function() {
console.log('Test -------->');
return 'Test';
};
importing.prototype.end = function() {
console.log('end -------->');
return 'end';
}
const scan = new importing();
scan.start().then(() => scan.end()).catch(geko => {
console.log('Error message: ',geko)
});
If the whole project was created as follows:
return new Promise(resolve => {
setTimeout(resolve, 1000);
}).then(() =>
this.test()
);
I would not have much to do with the classic JS procedure, ie the nesting of functions in the functions. That's what I want to avoid. I want content, an outline, when I look at it for a year and I'm going to solve the bugs, I'll know where to start and where the mistakes are going.
let importing = function(){
this.name = "boot";
}
importing.prototype.start = function(resolve){
console.log('Start');
this.resolve = resolve;
setTimeout(this.test.bind(this),1000);
}
importing.prototype.test = function(){
console.log('Test');
this.resolve('Test');
}
importing.prototype.end = function(resolve){
console.log('End');
resolve('end');
}
let scan = new importing();
let promise = function(arg){
return new Promise((resolve, reject)=>{ // The same: new Promise(scan[arg].bind(scan))
return scan[arg].bind(scan)(resolve)
});
}
// so I would imagine chaining. Few braces, simply
// In each called object, if possible, the minimum promissory note
Promise.resolve()
.then(geko => promise('start'))
.then(geko => promise('end'))
.catch(geko => {
console.log('Error message: ',geko)
})
The following implements a control flow wrapper co enabling asynchronous code to be delineated only by the yield keyword.
Is this basically what async/await does under the hood in ESwhatever?
co(function*() {
console.log('...');
yield one();
console.log('...');
yield two();
})
function co(gFn) {
var g = gFn();
return Promise.resolve()
.then(go);
function go() {
var result = g.next();
if(result.done) {
return;
}
if(isPromise(result.value)) {
return result.value.then(go); // Promises block until resolution.
}
return Promise.resolve(result);
}
}
function isPromise(o) {
return o instanceof Promise;
}
function one() {
return new Promise(resolve => setTimeout(() => (console.log('one'), resolve()), 1000));
}
function two() {
return new Promise(resolve => setTimeout(() => (console.log('two'), resolve()), 1000));
}
Edit:
In light of the responses I updated to take into consideration return values:
co(function*() {
console.log('...');
const result1 = yield one();
console.log('result1: ', result1);
const result2 = yield two();
console.log('result2: ', result2);
const result3 = yield[one(), two()];
console.log('result3: ', result3);
const result4 = yield{
one: one(),
two: two()
};
console.log('result4: ', result4);
})
function co(gFn) {
var g = gFn();
return Promise.resolve().then(go);
function go() {
var result = g.next(...arguments);
if (isPromise(result.value)) {
return result.value.then(go);
}
if (Array.isArray(result.value)) {
return Promise.all(result.value).then(go);
}
if (isObject(result.value)) {
var o = {};
var promises = Object.keys(result.value).map(k=>result.value[k].then(r=>o[k] = r));
return Promise.all(promises).then(()=>o).then(go);
}
return Promise.resolve(result);
}
}
function isPromise(o) {
return o instanceof Promise;
}
function isObject(val) {
return val && (Object === val.constructor);
}
function one() {
return new Promise(resolve=>setTimeout(()=>(console.log('one'),
resolve('result 1')), 1000));
}
function two() {
return new Promise(resolve=>setTimeout(()=>(console.log('two'),
resolve('result 2')), 1000));
}
Is this basically what async/await does under the hood in ESwhatever?
Not really. It's a different approach for doing sorta the same thing. What async/await turn into is more like
async function foo() {
const bar = await Bar();
bar++;
const baz = await Baz(bar);
return baz;
}
becomes
function foo() {
return Bar()
.then(bar => {
bar++;
return Baz(bar)
.then(baz => {
return baz;
});
});
}
Stage 3 Draft / January 26, 2016
Async Functions provides examples of three patterns; Promise; Generator; Async Functions; where the distinct approaches essentially produce the same result
Examples#
Take the following example, first written using Promises. This code
chains a set of animations on an element, stopping when there is an
exception in an animation, and returning the value produced by the
final succesfully executed animation.
function chainAnimationsPromise(elem, animations) {
let ret = null;
let p = currentPromise;
for(const anim of animations) {
p = p.then(function(val) {
ret = val;
return anim(elem);
})
}
return p.catch(function(e) {
/* ignore and keep going */
}).then(function() {
return ret;
});
}
Already with promises, the code is much improved from a straight callback style, where this sort of looping and exception handling is
challenging.
Task.js and similar libraries offer a way to use generators to further
simplify the code maintaining the same meaning:
function chainAnimationsGenerator(elem, animations) {
return spawn(function*() {
let ret = null;
try {
for(const anim of animations) {
ret = yield anim(elem);
}
} catch(e) { /* ignore and keep going */ }
return ret;
});
}
This is a marked improvement. All of the promise boilerplate above and beyond the semantic content of the code is removed, and the
body of the inner function represents user intent. However, there is
an outer layer of boilerplate to wrap the code in an additional
generator function and pass it to a library to convert to a promise.
This layer needs to be repeated in every function that uses this
mechanism to produce a promise. This is so common in typical async
Javascript code, that there is value in removing the need for the
remaining boilerplate.
With async functions, all the remaining boilerplate is removed,
leaving only the semantically meaningful code in the program text:
async function chainAnimationsAsync(elem, animations) {
let ret = null;
try {
for(const anim of animations) {
ret = await anim(elem);
}
} catch(e) { /* ignore and keep going */ }
return ret;
}