There is some Advanced topics in JS I am learning and there is something I am trying to understand.
I've written a VueJS application and I need to expose some data and possibly methods from Vue outside of Vue itself. I am using vuex.
This is so that users can use normal JS on the front end to extend the application.
The idea is the user would add something like this on the front end. I have seen several JS frameworks/apps that do something like this.
AppName.onReady.then(function(instance) {
// use the instance object
var email = instance.data["email"]
var name = instance.data["name"]
var isComplete = member.isComplete
})
Now what I am trying to understand is the code I would need in my App to make the above work.
I know from the code it is a Class AppName that is invoked with new AppName({}) and it has a promise that gets resolved but not sure how to accomplish it.
Here is a sample for returning a promise.
const AppName = {
onReady() {
return new Promise( (resolve, reject) => {
// do your stuff here, then call resolve().
// sample follows
setTimeout(()=>{resolve()}, 1000)
})
}
}
Call it like this. Make sure to invoke onReady(), not just onReady.
AppName.onReady().then(()=>{
// your code here.
})
Here is one way you can can achieve that effect:
class AppName {
constructor(data = {
email: null,
name: null
}) {
this.data = data;
}
get onReady() {
return new Promise((resolve, reject) => {
resolve(this);
});
}
}
const handleOnReady = instance => {
const {
email,
name
} = instance.data;
console.log(`${name}'s email address is: ${email}.`);
};
const tom = {
email: 'tom#fakemail.com',
name: 'Tom'
};
const app = new AppName(tom);
app.onReady.then(handleOnReady);
You can mark a method as async. Then it returns a promise implicitly.
const AppName = {
async onReady() {
// do your stuff here, then simply return to resolve.
// throwing a exception calls reject.
}
}
Usage:
AppName.onReady().then(()=>{
// app is ready
})
If your client is in an async function, you can simply use await
Usage:
// must be inside async function
await AppName.onReady()
// app is ready
To use await at root level, wrap it in immediate executed function:
(async()=>{
await AppName.onReady()
})()
Related
What I'm trying to accomplish
I am currently trying to create a wrapper for a db connection (to Neo4j) that works similar to the following:
Instantiate driver
Expose the executor for the driver so a session can be created
Pass my logic
Close the connection
Since there's no destructor in JavasScript, it's making this unnecessarily difficult with properly closing the session. The logic for creating and closing a connection is extremely repetitive and I'm trying to simplify repetitive scripts so that it's easier to call.
What I've tried.
Inject promise in chain
I thought something like the following could work, but I just cannot seem to create the logic properly. Passing session back to my inserted promise is challenging.
const connect = () => {
var driver;
var session;
return Promise.resolve(() => {
driver = my.driver(uri, creds);
}).then(() => {
// insert my promise here, exposing driver.session() function for executor
// if possible, bind it to the session var so we can properly close it after
// this would also determine the return value
}).catch((e) => console.error(e))
.finally(() => {
session.close();
driver.close();
})
});
Create class wrapper with procedural logic
I then also tried another approach, similar to:
var driver = my.driver(uri, creds);
var session;
function exitHandler(options) {
// ...
session.close();
driver.close();
}
// I have process.on for exit, SIGINT, SIGUSR1, SIGUSR2, and uncaughtException
process.on('exit', exitHandler.bind(null, options));
// ...
class Connection extends Promise<any> {
constructor(executor: Function) {
super((resolve, reject) => executor(resolve, reject));
executor(driver.session.bind(null, this));
}
}
export default Connection;
And calling it like
// ...
const handler = async () => await new Connection((session) => {
const s = session();
// do stuff here
});
The problem with this approach is that the driver is not instantiated before session is used (and so it's undefined). It also feels a little hacky with the process.on calls.
Question
Neither method works (or any of my other attempts). How can I properly wrap db connections to ensure they're consistent and deduplicate my existing code?
A sample of the Neo4j connection script can be found here. This is, essentially, what I'm trying to deduplicate across my scripts (pass everything from line 11 to 42 - inclusive) but have the init of driver, catch, finally, session.close(), driver.close() logic in my wrapper.
Ideally, I would like to expose the session function call so that I can pass parameters to it if needed: See the Session API for more info. If possible, I also want to bind the rxSession reactive session.
A sample of the Neo4j connection script can be found here. This is, essentially, what I'm trying to deduplicate across my scripts (pass everything from line 11 to 42 - inclusive) but have the init of driver, catch, finally, session.close(), driver.close() logic in my wrapper.
OK, the above part of what you are asking is what I was able to best parse and work with.
Taking the code you reference and factoring out lines 11 to 42 such that everything outside of those is shared and everything inside of those is customizable by the caller, this is what I get for the reusable part, designed to be in a module by itself:
// dbwrapper.js
const neo4j = require('neo4j-driver')
const uri = 'neo4j+s://<Bolt url for Neo4j Aura database>';
const user = '<Username for Neo4j Aura database>';
const password = '<Password for Neo4j Aura database>';
const driver = neo4j.driver(uri, neo4j.auth.basic(user, password));
let driverOpen = true;
async function runDBOperation(opCallback, sessOpts = {}) {
const session = driver.session(sessOpts);
try {
await opCallback(session);
} catch (e) {
console.log(e);
throw e;
} finally {
await session.close();
}
}
async function shutdownDb() {
if (driverOpen) {
driverOpen = false;
await driver.close();
}
}
process.on('exit', shutdownDb);
module.exports = { runDBOperation, shutdownDb };
Then, you could use this from some other module like this:
const { runDBOperation, shutdownDB } = require('./dbwrapper.js');
runDBOperation(async (session) => {
const person1Name = 'Alice'
const person2Name = 'David'
// To learn more about the Cypher syntax, see https://neo4j.com/docs/cypher-manual/current/
// The Reference Card is also a good resource for keywords https://neo4j.com/docs/cypher-refcard/current/
const writeQuery = `MERGE (p1:Person { name: $person1Name })
MERGE (p2:Person { name: $person2Name })
MERGE (p1)-[:KNOWS]->(p2)
RETURN p1, p2`
// Write transactions allow the driver to handle retries and transient errors
const writeResult = await session.writeTransaction(tx =>
tx.run(writeQuery, { person1Name, person2Name })
)
writeResult.records.forEach(record => {
const person1Node = record.get('p1')
const person2Node = record.get('p2')
console.log(
`Created friendship between: ${person1Node.properties.name}, ${person2Node.properties.name}`
)
})
const readQuery = `MATCH (p:Person)
WHERE p.name = $personName
RETURN p.name AS name`
const readResult = await session.readTransaction(tx =>
tx.run(readQuery, { personName: person1Name })
)
readResult.records.forEach(record => {
console.log(`Found person: ${record.get('name')}`)
})
}).then(result => {
console.log("all done");
}).catch(err => {
console.log(err);
});
This can be made more flexible or more extensible according to requirements, but obviously the general idea is to keep it simple so that simple uses of the common code don't require a lot of code.
I know there are many questions and answers based on Promise, but what I want to do is to retrieve some data with axios (to a microservice) and then to use this data in order to send another request (to a different microservice).
Somehow, I figured out how to set my request:
screenshot from console with the request right before axios call
The problem is that in backend I have only first two clauses. I think this is because I have used async/await in order to successfully avoid Promise and get the actual result/class. What I meant is that, maybe the request is sent before the promise is fulfilled, but how do I correctly get the request in console?
I am newbie into Javascript, so any helping hand is welcome.
EDIT:
Here is my code:
getServicesList = async (instanceIds) => {
return await FlowsInventoryAPI.searchServices(instanceIds, this.props.salesline, this.props.environment, this.props.sources, this.props.targets)
.then((response) => {
return response;
})
.catch((error) => {
Toastr.error(ResponseErrorProvider.getError(error));
if (ResponseErrorProvider.isUnauthorized(error)) {
Toastr.error(error.response.data.message);
this.props.onLogout();
}
});
}
The above one is the first call I've talked about.
buildSearchObject = (size, page, status) => {
let interval = TimestampUtils.getInterval(this.props.logsTimeInterval);
let from = interval.from * 1000;
let to = interval.to * 1000;
return {
fromMillis: from,
toMillis: to,
size: size,
page: page,
salesline: this.props.salesline,
environment: this.props.environment,
routes: this.props.routes,
sources: this.props.sources,
targets: this.props.targets,
customFilters: [...this.props.filters.values(), ...this.getEnabledAdvancedFilters().values()],
status: status === LogStatus.ALL ? "" : status,
sortFieldName: this.props.sortColumn,
order: this.props.sortOrder,
searchFilter: this.props.searchFilter,
searchFilterOperator: this.props.searchFilterOperator,
applications: this.props.applications,
openedStores: this.props.openedStores,
servicesPromise: this.state.servicesList // here is the promise
}
};
searchLogs = (size, page, status, callback) => {
loadingService.showLoadingModal("loadingLogsPage", this.props.location.pathname);
let searchObject = this.buildSearchObject(size, page, status);
ElasticSearchApi.search(searchObject, this.props.token)
.then(response => {
callback(response);
})
.catch((error) => {
loadingService.hideLoadingModal("loadingLogsPage", this.props.location.pathname);
Toastr.error(ResponseErrorProvider.getError(error));
if (ResponseErrorProvider.isUnauthorized(error)) {
Toastr.error(error.response.data.message);
this.props.onLogout();
}
});
};
I have the second call in last paragraph which calls the buildSearchObject method which contains our promise. As I told you I figured out how to send it as value, but I think that because of "asynchronicity" maybe my promise is not ready yet in the moment when second call is called, this is why my code has the promise in state.
EDIT 2:
constructor(props) {
super(props);
this.ongoingRequestId = undefined;
this.ongoingRequests = new Map();
this.state = {
servicesList: this.getServicesList(this.getInstanceIds())
}
}
Here is my constructor, where I create my this.state.servicesList.
Some advice:
Do not mix traditional promises syntax with async / await. It will make your code hard to understand, even for yourself. Do not mix either callback approach with promises. Choose one approach and stick to it.
If you are having a hard time with promises, force yourself to use async / await everywhere. async / await is easier to understand in my opinion, because it doesn't break your code flow.
For instance, transform this:
FlowsInventoryAPI.searchServices(/* params */)
.then((response) => /* response code */)
.catch((error) => /* error code */)
to:
try {
const response = await FlowsInventoryAPI.searchServices(/* params */);
/* response code */
} catch (error) {
/* error code */
}
Do not make your constructors asynchronous like you do where you call this.getServicesList, because you cannot wait for an asynchronous operation (like getServicesList) inside a constructor. Use instead a static async method.
For instance, transform this:
class SomeObject extends Something {
constructor(props) {
super(props);
this.ongoingRequestId = undefined;
this.ongoingRequests = new Map();
this.state = {
servicesList: this.getServicesList(this.getInstanceIds())
}
}
}
to:
class SomeObject extends Something {
constructor(props) {
super(props);
this.ongoingRequestId = undefined;
this.ongoingRequests = new Map();
this.state = { servicesList: null };
}
async init() {
this.state.servicesList = await this.getServicesList(this.getInstanceIds());
}
static async create(props) {
const someObject = new SomeObject(props);
await someObject.init();
return someObject;
}
}
Instead of calling const object = new SomeObject(props);, do const object = await SomeObject.create(props);
You will need to use await keyword to wait for a promise response before continuing.
// 1. Wait for create or update the customer before continuing
const customerId = await updateOrCreateCustomer(customerData);
// 2. Register sale, with customer created in previous section
const customerSale = sale(paymentMethod, customerId);
Read more about the await keyword
I have a childClass and limited access to the parentClass. Within one of the childClass methods I need to invoke a parentClass method, getToken(). getToken(), and the rest of the parentClass, is written using callbacks. The child class is written with promises and async-await.
I know that's messy but this is the only spot where there are issues right now and this is what we need to do.
Example of issue
...
try {
// getToken is not promisified yet so it doesn't work
let tokens = await this.getToken();
refreshToken = tokens.refresh_token;
}
...
Can I make a custom "promisifying" function? Something like,
// This isn't correct even if the idea were ok
async promisify(function) {
return new Promise((resolve, reject) => {
function((err, data) => {
if (err) reject(err);
else resolve(data);
})
})
}
Then in my code write:
...
try {
// getToken is maybe promisified
let tokens = await this.promisify(this.getToken());
refreshToken = tokens.refresh_token;
}
...
Or would it looks something like
const util = require('util');
const asyncGetToken = util.promisify(parentClass.getToken);
...
try {
let tokens = await asyncGetToken();
refreshToken = tokens.refresh_token;
}
...
If so, would I have the right instance of the childClass to work with?
I know that's messy but thanks for any tips.
I'm writing an AudioRecorder class whose init() accesses Navigator.mediaDevices.getUserMedia({audio: true}).
Is there a way to accept the user permission request in the DOM from Jasmine?
Source:
export default class AudioRecorder {
async init() {
const audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
this.recorder = new MediaRecorder(audioStream)
}
Jasmine test:
it('init()', done => {
const record = new AudioRecorder();
record.init().then(() => {
done();
});
});
You should be spying on navigator.mediaDevices.getUserMedia, forcing it to return a promise and resolving it with the value that you want.
You can put something like this in a beforeEach block, or at the beginning of the test:
const audioStream = { ... }; // create a mock audio stream with the appropriate methods spied on
let promise = Promise.resolve(audioStream);
spyOn(navigator.mediaDevices, 'getUserMedia', promise);
I'd also recommend that you convert your test to async, like this:
it('init()', async done => {
const record = new AudioRecorder();
const result = await record.init();
// test something with the result of init
done();
});
I think it is much easier to follow the control flow here.
Ok so i've searched around and found nothing related to this problem.
My problem is something like this ->
I create an object (push into array) with some info taken from an api. After getting the info from the api i need to call yet another API to get further information on users. Since there are multiple keys for users i'd like to be able to set them inline with a simple function.
I'm doing something like this ->
_item.push({
Author: setPeople(item.Author.Title),
Title: item.Title,
....
Requester: setPeople(item.Requester.Title
})
At the moment i am getting the promise set(entirely) and not the PromiseValue. I know you usually do something like setPeople(name).then(() => {}) however that is not working in my object (sets the key too fast).
Any tip on how i should approach this?
Updating with more code.
export const retrieveIrfItems = async (spId) => {
let spQuery = "SITE" + SpQueryExtend1 + spQueryExpand;
return new Promise((resolve, reject) => {
let _items = [];
axiosApi.get(SiteUrl + spQuery).then((response) => {
//console.log(response.data.d);
return response.data.d;
}).then(async (item) => {
//let requesterSP = setPeople()
const createSPUser = async (user) => {
let spUser;
console.log("User prop is");
console.log(user);
setPeople(user).then((item) => {
spUser = item;
});
return spUser;
}
_item.push({
Author: setPeople(item.Author.Title),
Title: item.Title,
....
Requester: setPeople(item.Requester.Title
})
Ignore the unused function, i'm still doing tests to find a way for this problem.
Found the fix thanks to comments.
Using async/await wouldnt help me since i'd still get promise pending or undefined. What i had to use is ->
Requester: await setPeople(item.Requester.Title).then((user) => { return user }),
Using that in my object seems to work, but my question is...how good is this approach? If there are lots of fields with this behaviour (currently 5), wouldnt that slow down the page by...a lot ?
Then you should try something like that :
export const retrieveIrfItems = async (spId) => {
return new Promise((resolve, reject) => {
let spQuery = "SITE" + SpQueryExtend1 + spQueryExpand;
let _items = [];
try{
const axiosApiItem = await axiosApi.get(SiteUrl + spQuery);
const item = axiosApiItem.data.d;
_item.push({
Author: await setPeople(item.Author.Title),
Title: item.Title,
...
Requester: await setPeople(item.Requester.Title)
});
return resolve(_items);
}catch(e){
return reject(e);
}
});
}
Async / await is a way to consume async Promise functions. The async keyword tells the javascript compiler that this function will consume one or more asynchronous functions. The await keyword tells the compiler that the next function call is asynchronous and returns a promise.
In your code, your first then() function should return a promise which is not the case, that's why the second then() can't be reached.
Also, in your code, your new Promise doesn't return anything. When you create a new Promise, you have to end it by calling resolve() or reject.
Hope it helps...