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();
Related
I have an async function getDataItem that returns a promise and passes data to another function preparePhysicianSchemaData which builds a global object physicianDetailsObj out of the passed data as well as data fetched after calling yet another async function inside it for each row of data it was initially passed.
getDataItem(listID, itemID).then(preparePhysicianSchemaData)
Only after the global object variable physicianDetailsObj is fully populated, then I need to call another function called buildSEOSchemaBlock(), whose job is to parse physicianDetailsObj object and build the final object needed.
I would rather not use setTimeOut to try to time this:
setTimeout(function(){ return getListItem(listID, itemID).then(preparePhysicianSchemaData) }, 10);
setTimeout(function(){ return buildPhysicianSchemaBlock() }, 3000);
How can I chain the last function like this: getDataItem(listID, itemID).then(preparePhysicianSchemaData).then(buildPhysicianSchemaBlock) ensuring that the last function runs only after the global object variable physicianDetailsObj is fully populated?
var physicianDetailsObj = {};
function getListItem() {} //returns promise
function preparePhysicianSchemaData(item) {
var tempPhysicianDetailsObj = {};
var currentPhysicianItemId = item.get_id();
tempPhysicianDetailsObj = {
"name" : item.get_item("Title"),
"url" : item.get_item("SEOCanonicalHref").match('href="([^"]+)')[1]
};
var currentItemPhysicianTag= item.get_item("PhysicianItemTag").get_label();
getPhysicianLocationDetailsFromServiceLocations(currentItemPhysicianTag).then(function(slitems) {
console.log(slitems);
var slitemEnum = slitems.getEnumerator();
//first empty the object
Object.keys(physicianDetailsObj).forEach(k => delete physicianDetailsObj[k]);
while (slitemEnum.moveNext()) {
var slitem = slitemEnum.get_current();
physicianDetailsObj[currentPhysicianItemId + '-' + slitem.get_id()] = {
"name": tempPhysicianDetailsObj["name"],
"image": tempPhysicianDetailsObj["image"],
"url": tempPhysicianDetailsObj["url"],
"streetAddress": slitem.get_item("LocationAddress"),
"addressLocality": slitem.get_item("LocationLU_x003A_LocationCity").get_lookupValue()
}
}
});
}
function buildSEOSchemaBlock(){ } //process physicianDetailsObj
getPhysicianLocationDetailsFromServiceLocations is an async function which is called inside preparePhysicianSchemaData
If preparePhysicianSchemaData is synchronous then you don't need to await it, just perform the operation after it. Something like this:
getListItem(listID, itemID).then(function() {
preparePhysicianSchemaData();
buildPhysicianSchemaBlock();
});
Or if there are results you need from the Promise, something like:
getListItem(listID, itemID).then(function(result) {
preparePhysicianSchemaData(result);
buildPhysicianSchemaBlock();
});
If it's asynchronous then you can chain the Promises, something like:
getListItem(listID, itemID)
.then(function(result) { return preparePhysicianSchemaData(result); })
.then(function(newResult) { return buildPhysicianSchemaBlock(newResult); });
Basically each call to .then() passes the result of the previous Promise to the new asynchronous function, returning that function's Promise.
If you need to execute functions in strict order one after another, use async/await and Promises, have a look at this demo
// Use async
(async () => {
// Function 1
const fn1 = (val) => {
return new Promise((resolve, reject) => {
// Do some stuff here
val = val + 1;
// Resolve result.
// Can be resolved from any level
// of nested function!
function nested1() {
function nested2() {
resolve(val);
}
nested2();
}
nested1();
});
};
// Function 2
const fn2 = (val) => {
return new Promise((resolve, reject) => {
// Do some stuff here
val = val * 2;
// Resolve result
resolve(val);
});
};
// Function 3
const fn3 = (val) => {
return new Promise((resolve, reject) => {
// Do some stuff here
val = val + 1000;
// Resolve result
resolve(val);
});
};
// Async code
let val = 5;
val = await fn1(val); // Wait until fn1 resolves
val = await fn2(val); // Wait until fn2 resolves
val = await fn3(val); // Wait until fn3 resolves
console.log(val);
})();
I have an async generator function that I would like to just get the return value for without calling next a bunch of times. Right now I have something like this:
async function *getData(): AsyncGenerator<object[], object[]>
{
let results = []
let res = {};
while (true)
{
res = await apiCall(res.next);
results.push(...res.data)
if (!res.next) break;
yield res.data;
}
return results;
}
async function main()
{
const genFn = getData();
while (!(await genFn.next()).done) {}
}
Is there a better/more concise way that doesn't need the while loop?
There is not much you can do besides creating a function that does it for you to clean things up. You can use a for-await instead of a while loop though.
async function drainAsyncIterable(iterable) {
const iterator = iterable[Symbol.asyncIterator]();
while (!(await iterator.next()).done) {}
}
vs
async function drainAsyncIterable(iterable) {
for await (const _ of iterable) {}
}
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);
}
}
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;
}
I am learning promises, so I decided to play around with extending them. Everything is working except I am having a hard time figuring out how to make a value persist across all functions.
My goal is to find the count of the called functions from this promise, but each call creates a new one and I am having trouble finding a way to pass the value in. I tried adding a constructor which will have a value passed in, but it doesn't seem to work as I expect. I assume this is due to me misunderstanding the scope of "this".
to sum it up, each of my functions "init", "add" and "commit" should all up front add 1 to the "total number of steps" variables, which right now in this example is "i". With that I want to be able to say, I am on step 1 of 3, step 2 of 3 etc...
class Repo {
constructor(setup) {
this.s = {};
this.s._progress = { "total":0, "count":0 };
this.s._logging = { "enabled":true, "location":"console", "pretty":true, "verbose":false, "tabCount":0 };
this.s._remoteInfo = { "name":"", "url":"" };
this.s._localInfo = { "path":"" };
this.s._logReset = () => {
this.s._logging.tabCount = 0;
};
this.s._log = (message, tabCount) => {
if(this.s._logging.enabled) {
let tabs = '';
if(this.s._logging.pretty) {
for(let i = 0; i < tabCount; i++) { tabs = tabs + '\t' };
}
if(this.s._logging.location == 'console') { console.log(tabs, message); }
else {
//TODO: implement the file location to output
}
}
};
this.s._progressReset = () => {
this.s._progress.total = 0;
this.s._progress.count = 0;
};
this.s._addProgressTotal = () => {
this.s._progress.total++;
console.log(this.s._progress.total)
}
this.s._addProgress = () => {
this.s._progress.count++;
console.log('Progress is ' + this.s._progress.count + ' out of ' + this.s._progress.total)
}
}
//Starts the promise chain and passes in the settings to be used.
start() {
this.s._logReset();
this.s._progressReset();
return new RepoPromise((resolve, reject) => {
this.s._log('Start Log: <time>',0)
resolve(this.s);
});
}
}
class RepoPromise extends Promise {
constructor(executor, val) {
let e = executor || function (res, rej) { res('')};
super((resolve, reject) => {
return e(resolve, reject);
});
this.i = val || 0;
}
end() {
const returnValue = super.then((s) => {
return new RepoPromise((resolve, reject) => {
s._log('End Log: <time>',0)
resolve(s);
}, this.i);
});
return returnValue;
}
init() {
//I know I need to add 1 to "i" here, but it won't work
const returnValue = super.then((s) => {
return new RepoPromise((resolve, reject) => {
s._log('git init',1);
s._addProgress();
resolve(s, '')
}, ++this.i);
});
return returnValue;
};
add() {
//I know I need to add 1 to "i" here, but it won't work
const returnValue = super.then((s) => {
return new RepoPromise((resolve, reject) => {
setTimeout(() => {
s._log('git add',1);
s._addProgress();
resolve(s,'');
//reject('Add Failed')
}, Math.random() * (10000 - 1000) + 1000);
},++this.i);
});
return returnValue;
}
commit() {
//I know I need to add 1 to "i" here, but it won't work
const returnValue = super.then((s) => {
return new RepoPromise((resolve, reject) => {
setTimeout(() => {
s._log('git commit -m "message"',1);
s._addProgress();
resolve(s, 'Finished');
}, Math.random() * (5000 - 1000) + 1000);
}, ++this.i);
});
return returnValue;
}
then(onFulfilled, onRejected) {
const returnValue = super.then(onFulfilled, onRejected);
return returnValue;
}
}
usage:
var p = new Repo('')
.start()
.init()
.add()
.commit()
.end()
.catch(
x => {console.log('it broke: ' + x)}
);
As you've pointed out, there isn't one promise in the chain, every then and catch returns a new promise. So don't try to retain the state in the RepoPromise, retain it in the object that you pass through the chain as the resolution value: s.
Re your second parameter to RepoPromise constructor: You can't reliably do that, because you aren't in control of every time that constructor is called. Remember, that constructor is called when you call then or catch. Which is another reason to pass the value around on s instead. :-) Just for completeness, here's an illustration of the fact that the constructor gets called within Promise:
class MyPromise extends Promise {
constructor(...args) {
super(...args);
console.log("MyPromise constructor called");
}
}
MyPromise.resolve()
.then(val => val)
.then(val => val)
.then(val => val);
A couple of side notes:
This:
super((resolve, reject) => {
return e(resolve, reject);
});
can be written simply as:
super(e);
This doesn't do anything and can just be removed:
then(onFulfilled, onRejected) {
const returnValue = super.then(onFulfilled, onRejected);
return returnValue;
}
I was a bit dense understanding the question, but now I get it: You want to increase s._progress.total for each call to init/add/commit, and increase s._progress.count when each then/catch callback is called.
Here's a simplified exmaple that just uses then and catch rather than adding init, add, and commit but you can easily apply the pattern to add those if you like.
The solution was to keep the status tracker (s) on the promise object, and insert ourselves into the various ways new promises are created (then, catch) so we copy the tracker from the old promise to the new one. We share the tracker across all of these promises, e.g., the tracker from the root promise tracks everything from there forward. See comments:
"use strict";
// For tracking our status
class Status {
constructor(total = 0, count = 0) {
this.id = ++Status.id;
this.total = total;
this.count = count;
}
addCall() {
++this.total;
return this;
}
addProgress() {
++this.count;
return this;
}
toString() {
return `[S${this.id}]: Total: ${this.total}, Count: ${this.count}`;
}
}
Status.id = 0;
// The promise subclass
class RepoPromise extends Promise {
constructor(executor) {
super(executor);
this.s = new Status();
}
// Utility method to wrap `then`/`catch` callbacks so we hook into when they're called
_wrapCallbacks(...callbacks) {
return callbacks.filter(c => c).map(c => value => this._handleCallback(c, value));
}
// Utility method for when the callback should be called: We track that we've seen
// the call then execute the callback
_handleCallback(callback, value) {
this.s.addProgress();
console.log("Progress: " + this.s);
return callback(value);
}
// Standard `then`, but overridden so we track what's going on, including copying
// our status object to the new promise before returning it
then(onResolved, onRejected) {
this.s.addCall();
console.log("Added: " + this.s);
const newPromise = super.then(...this._wrapCallbacks(onResolved, onRejected));
newPromise.s = this.s;
return newPromise;
}
// Standard `catch`, doing the same things as `then`
catch(onRejected) {
this.s.addCall();
console.log("Added: " + this.s);
const newPromise = super.catch(...this._wrapCallbacks(onRejected));
newPromise.s = this.s;
return newPromise;
}
}
// Create a promise we'll resolve after a random timeout
function delayedGratification() {
return new Promise(resolve => {
setTimeout(_ => {
resolve();
}, Math.random() * 1000);
});
}
// Run! Note we follow both kinds of paths: Chain and diverge:
const rp = RepoPromise.resolve();
rp.then(delayedGratification) // First chain
.then(delayedGratification)
.then(delayedGratification);
rp.catch(delayedGratification) // Second chain
.then(delayedGratification);
.as-console-wrapper {
max-height: 100% !important;
}