exporting multiple functions but always run default function first - javascript

Sorry for the bad description but I would like to create a "service.js" -file where I export multiple functions that retrieve data from a database.
something like:
...
import Person from '../models/Person'
import dbConnect from "../lib/dbConnect";
await dbConnect()
const save = async (person) => {
const savedPerson = await Person.save(person)
return savedPerson
}
const geAll = async () => {
const persons = await Person.find({})
return persons
}
...
export default { getAll, save };
But how do I always run dbConnect() when the exported functions are used in the code ? do I need to add the await dbConnect() to every function or is there some smarter way to do this ?

What about a class with await dbConnect() on constructor? Something like:
class MyClass {
constructor() {
return (async () => {
await dbConnect();
return this; // new instance created
})();
}
async save(person) {
const savedPerson = await Person.save(person)
return savedPerson
}
async geAll() {
const persons = await Person.find({})
return persons
}
}
Then:
const myClass = await new MyClass(); // <-- this calls dbConnect()
myClass.geAll();

Related

chained await in class chained methods

context: Two javascript classes in separate files, each integrating a different external service and being called in a express.js router.
See "problematic code" below:
route
routes.post('/aws', upload.single('file'), async (req, res) => {
const transcribeParams = JSON.parse(req.body.options)
const bucket = 'bucket-name'
const data = await ( await ( await awsTranscribe.Upload(req.file, bucket)).CreateJob(transcribeParams)).GetJob()
res.send(data)
})
S3 class
class AmazonS3 {
constructor() {
this.Upload = this.Upload
}
async Upload(file, bucket) {
const uploadParams = {
Bucket: bucket,
Body: fs.createReadStream(file.path),
Key: file.filename,
}
this.data = await s3.upload(uploadParams).promise()
return this
}
}
Transcribe class
class Transcribe extends AwsS3 {
constructor() {
super()
this.CreateJob = this.CreateJob
this.GetJob = this.GetJob
}
async CreateJob(params) {
if(this.data?.Location) {
params.Media = { ...params.Media, MediaFileUri: this.data.Location }
}
this.data = await transcribeService.startTranscriptionJob(params).promise()
return this
}
async GetJob(jobName) {
if(this.data?.TranscriptionJob?.TranscriptionJobName) {
jobName = this.data.TranscriptionJob.TranscriptionJobName
}
this.data = await transcribeService.getTranscriptionJob({TranscriptionJobName: jobName}).promise()
return this
}
}
problem: the problem is with the chained awaits in the router file:
await ( await ( await awsTranscribe.Upload...
Yes, it does work, but it would be horrible for another person to maintain this code in the future.
How can i make so it would be just
awsTranscribe.Upload(req.file, bucket).CreateJob(transcribeParams).GetJob() without the .then?
The problem is with the chained awaits in the router file: await ( await ( await awsTranscribe.Upload...
No, that's fine. In particular it would be trivial to refactor it to separate lines:
routes.post('/aws', upload.single('file'), async (req, res) => {
const transcribeParams = JSON.parse(req.body.options)
const bucket = 'bucket-name'
const a = await awsTranscribe.Upload(req.file, bucket);
const b = await b.CreateJob(transcribeParams);
const c = await b.GetJob();
res.send(c);
});
Your actual problem is that a, b, and c all refer to the same object awsTranscribe. Your code would also "work" if it was written
routes.post('/aws', upload.single('file'), async (req, res) => {
const transcribeParams = JSON.parse(req.body.options)
const bucket = 'bucket-name'
await awsTranscribe.Upload(req.file, bucket);
await awsTranscribe.CreateJob(transcribeParams);
await awsTranscribe.GetJob();
res.send(awsTranscribe);
});
The horrible thing is that you are passing your data between these methods through the mutable awsTranscribe.data property - even storing different kinds of data in it at different times! One could change the order of method calls and it would completely break in non-obvious and hard-to-debug ways.
Also it seems that multiple requests share the same awsTranscribe instance. This will not work with concurrent requests. Anything is possible from just "not working" to responding with the job data from a different user (request)! You absolutely need to fix that, then look at ugly syntax later.
What you really should do is get rid of the classes. There's no reason to use stateful objects here, this is plain procedural code. Write simple functions, taking parameters and returning values:
export async function uploadFile(file, bucket) {
const uploadParams = {
Bucket: bucket,
Body: fs.createReadStream(file.path),
Key: file.filename,
};
const data = s3.upload(uploadParams).promise();
return data.Location;
}
export async function createTranscriptionJob(location, params) {
params = {
...params,
Media: {
...params.Media,
MediaFileUri: location,
},
};
const data = await transcribeService.startTranscriptionJob(params).promise();
return data.TranscriptionJob;
}
async function getTranscriptionJob(job) {
const jobName = job.TranscriptionJobName;
return transcribeService.getTranscriptionJob({TranscriptionJobName: jobName}).promise();
}
Then you can import and call them as
routes.post('/aws', upload.single('file'), async (req, res) => {
const transcribeParams = JSON.parse(req.body.options)
const bucket = 'bucket-name'
const location = await uploadFile(req.file, bucket);
const job = await createTranscriptionJob(location, transcribeParams);
const data = await getTranscriptionJob(job);
res.send(c);
});
I got interested in whether it was possible to take an object with several async methods and somehow make them automatically chainable. Well, you can:
function chain(obj, methodsArray) {
if (!methodsArray || !methodsArray.length) {
throw new Error("methodsArray argument must be array of chainable method names");
}
const methods = new Set(methodsArray);
let lastPromise = Promise.resolve();
const proxy = new Proxy(obj, {
get(target, prop, receiver) {
if (prop === "_promise") {
return function() {
return lastPromise;
}
}
const val = Reflect.get(target, prop, receiver);
if (typeof val !== "function" || !methods.has(prop)) {
// no chaining if it's not a function
// or it's not listed as a chainable method
return val;
} else {
// return a stub function
return function(...args) {
// chain a function call
lastPromise = lastPromise.then(() => {
return val.apply(obj, args);
//return Reflect.apply(val, obj, ...args);
});
return proxy;
}
}
}
});
return proxy;
}
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
function log(...args) {
if (!log.start) {
log.start = Date.now();
}
const delta = Date.now() - log.start;
const deltaPad = (delta + "").padStart(6, "0");
console.log(`${deltaPad}: `, ...args)
}
class Transcribe {
constructor() {
this.greeting = "Hello";
}
async createJob(params) {
log(`createJob: ${this.greeting}`);
return delay(200);
}
async getJob(jobName) {
log(`getJob: ${this.greeting}`);
return delay(100);
}
}
const t = new Transcribe();
const obj = chain(t, ["getJob", "createJob"]);
log("begin");
obj.createJob().getJob()._promise().then(() => {
log("end");
});
There's a placeholder for your Transcribe class that has two asynchronous methods that return a promise.
Then, there's a chain() function that returns a proxy to an object that makes a set of passed in method names be chainable which allows you to then do something like this:
const t = new Transcribe();
// make chainable proxy
const obj = chain(t, ["getJob", "createJob"]);
obj.createJob().getJob()
or
await obj.createJob().getJob()._promise()
I wouldn't necessarily say this is production-ready code, but it is an interesting feasibility demonstration and (for me) a chance to learn more about a Javascript proxy object.
Here's a different approach that (instead of the proxy object) adds method stubs to a promise to make things chainable:
function chain(orig, methodsArray) {
let masterP = Promise.resolve();
function addMethods(dest) {
for (const m of methodsArray) {
dest[m] = function(...args) {
// chain onto master promise to force sequencing
masterP = masterP.then(result => {
return orig[m].apply(orig, ...args);
});
// add methods to the latest promise befor returning it
addMethods(masterP);
return masterP;
}
}
}
// add method to our returned promise
addMethods(masterP);
return masterP;
}
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
function log(...args) {
if (!log.start) {
log.start = Date.now();
}
const delta = Date.now() - log.start;
const deltaPad = (delta + "").padStart(6, "0");
console.log(`${deltaPad}: `, ...args)
}
class Transcribe {
constructor() {
this.greeting = "Hello";
this.cntr = 0;
}
async createJob(params) {
log(`createJob: ${this.greeting}`);
++this.cntr;
return delay(200);
}
async getJob(jobName) {
log(`getJob: ${this.greeting}`);
++this.cntr;
return delay(100);
}
}
const t = new Transcribe();
log("begin");
chain(t, ["getJob", "createJob"]).createJob().getJob().then(() => {
log(`cntr = ${t.cntr}`);
log("end");
});
Since this returns an actual promise (with additional methods attached), you can directly use .then() or await with it without the separate ._promise() that the first implementation required.
So, you can now do something like this:
const t = new Transcribe();
chain(t, ["getJob", "createJob"]).createJob().getJob().then(() => {
log(`cntr = ${t.cntr}`);
});
or:
const t = new Transcribe();
await chain(t, ["getJob", "createJob"]).createJob().getJob();
log(`cntr = ${t.cntr}`);
And, here's a third version where it creates a thenable object (a pseudo-promise) with the added methods on it (if it bothers you to add methods to an existing promise):
function chain(orig, methodsArray) {
if (!methodsArray || !methodsArray.length) {
throw new Error("methodsArray argument must be array of chainable method names");
}
let masterP = Promise.resolve();
function makeThenable() {
let obj = {};
for (const m of methodsArray) {
obj[m] = function(...args) {
// chain onto master promise to force sequencing
masterP = masterP.then(result => {
return orig[m].apply(orig, ...args);
});
return makeThenable();
}
}
obj.then = function(onFulfill, onReject) {
return masterP.then(onFulfill, onReject);
}
obj.catch = function(onReject) {
return masterP.catch(onReject);
}
obj.finally = function(onFinally) {
return masterP.finally(onFinally);
}
return obj;
}
return makeThenable();
}
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
function log(...args) {
if (!log.start) {
log.start = Date.now();
}
const delta = Date.now() - log.start;
const deltaPad = (delta + "").padStart(6, "0");
console.log(`${deltaPad}: `, ...args)
}
class Transcribe {
constructor() {
this.greeting = "Hello";
this.cntr = 0;
}
async createJob(params) {
log(`createJob: ${this.greeting}`);
++this.cntr;
return delay(200);
}
async getJob(jobName) {
log(`getJob: ${this.greeting}`);
++this.cntr;
return delay(100);
}
}
const t = new Transcribe();
log("begin");
chain(t, ["getJob", "createJob"]).createJob().getJob().then(() => {
log(`cntr = ${t.cntr}`);
log("end");
});

Factory function for mongoose transaction session on Typescript

I have a lot of function like these
currency.service.ts
async createCurrencyOnSession(createCurrencyDto: CreateCurrencyDto): Promise<ResponseCurrencyDto> {
const session = await this.currencyModel.startSession();
session.startTransaction();
try {
const currency = await this.createCurrency(createCurrencyDto, {session});
await session.commitTransaction();
return currency;
} finally {
await session.endSession();
}
}
user.service.ts
async createUserOnSession(createUserDto: CreateUserDto): Promise<ResponseUserDto> {
const session = await this.userModel.startSession();
session.startTransaction();
try {
const user = await this.createUser(createUserDto, {session});
await session.commitTransaction();
return user;
} finally {
await session.endSession();
}
}
I don't like many try catch on code so I try to edit
currency.service.ts
async createCurrencyOnSession(
createCurrencyDto: CreateCurrencyDto,
): Promise<CurrencyResponseDto> {
const session = await this.currencyModel.startSession();
session.startTransaction();
return handleSession(this.createCurrency(createCurrencyDto, { session }));
}
export const handleSession =
async (handler) => async (dto, options: ISessionOptions) => {
try {
return await handler(dto, options);
} finally {
await options.session.endSession();
}
};
I can see error on Typescript because the main function return a specific interface: CurrencyResponseDto, UserResponseDto. How can I add dynamic interface to return CurrencyResponseDto, UserResponseDto, ... on factory function.
Could you help me to make it clean or suggest a better version on problem. Thanks

jest test async class method

class person {
constructor() {
...
}
fetchPersonData() {
fetch(api).then((return response) => {
return response;
})
}
async initializePerson() {
const data = await fetchPersonData();
}
}
I'm trying to write a test for initializePerson function but it doesn't get called.
test("pass initializePerson", async( ) => {
const personInstance = new person();
let spy = jest.spyOn(personInstance, "initializePerson").mockImplementationOnce(async () => {
return mock;
});
await personInstance.initializePerson();
expect(spy).toBeCalledTimes(1);
});
There are few mistakes.
(return response) is wrong.
You're spying the method which you're calling. It doesn't make any sense. Spy methods which you've used inside the methods before calling the actual method.
I think you're looking for something like this.
class Person {
constructor() {...}
fetchPersonData() {
return fetch(api).then(response => response.json())
}
async initializePerson() {
const data = await fetchPersonData();
}
}
and test.js
test("pass initializePerson", async () => {
const personInstance = new Person();
const fetchPersonDataSpy = jest.spyOn(personInstance, "fetchPersonData").mockResolvedValue({...})
await personInstance.initializePerson();
expect(fetchPersonDataSpy).toBeCalledTimes(1);
});
If you want test the other method and mock the fetch, read this article. This will help you!

Export a variable from a function in Javascript

All i want is take userId variable's value to use anything.
async function getMyData(){
const token = '';
spotifyApi.setAccessToken(token);
const data = await spotifyApi.getMe();
let userId = data.body.id;
return userId;
}
const userId = getMyData();
console.log(userId)
console.log(userId) saying me this: {Promise <pending<}
With any async functions, don't forget to use await before function execution
/// ./getMyData.js
export async function getMyData(){
// ...
return userId;
}
/// ./otherFile.js
import {getMyData} from './getMyData.js'
(async function () {
const userId = await getMyData();
console.log(userId)
})()
you are using async await features in your code.
Thus the return type of your getMyData would actually be a Promise object.
More about Promises here
Now, heres how you can use your function in another file without having to use the await keyword.
import { getMyData } from '%your file path%';
getMyData().then(function(userId) {
// use the value of userId here
console.log(userId);
}

How to wait for constructor to finish?

I have a class constructor that has async elements. Later when I create an instance of this class, I want to read a property that will only exist when the constructor finished 100%. I always run into problem Can not read property 'id' of undefined. I'm almost sure this is a problem about async .. await.
class NewPiecePlease {
constructor(IPFS, OrbitDB) {
this.OrbitDB = OrbitDB;
(async () => {
this.node = await IPFS.create();
// Initalizing OrbitDB
this._init.bind(this);
this._init();
})();
}
// This will create OrbitDB instance, and orbitdb folder.
async _init() {
this.orbitdb = await this.OrbitDB.createInstance(this.node);
console.log("OrbitDB instance created!");
this.defaultOptions = { accessController: { write: [this.orbitdb.identity.publicKey] }}
const docStoreOptions = {
...this.defaultOptions,
indexBy: 'hash',
}
this.piecesDb = await this.orbitdb.docstore('pieces', docStoreOptions);
await this.piecesDb.load();
}
...
}
Later I create an instance of this class like this:
(async () => {
const NPP = new NewPiecePlease;
console.log(NPP.piecesDb.id);
// This will give 'undefined' error
})();
How can I tell NodeJS that I want new NewPiecePlease to fully finish? await console.log(NPP.piecesDb.id); does not help, which is understandable, because it won't understand what I'm await-ing. What is the correct way to do this?
You could use a factory for this. They are very good for doing complex, potentially async object creation and to keep your constructors clean and focused.
class NewPiecePlease {
constructor(orbitdb, node, pieceDB) {
this.orbitdb = orbitdb;
this.node = node;
this.pieceDB = pieceDB;
}
static async create(IPFS, OrbitDB) {
const node = await IPFS.create();
const orbitdb = await OrbitDB.createInstance(node);
console.log("OrbitDB instance created!");
const defaultOptions = {
accessController: {
write: [orbitdb.identity.publicKey]
}
}
const docStoreOptions = { ...defaultOptions, indexBy: 'hash' };
const piecesDb = await orbitdb.docstore('pieces', docStoreOptions);
await piecesDb.load();
return new NewPiecePlease(orbitdb, node, piecedb);
}
}
As you can see the create method does all the async stuff and just passes the results into the constructor where it really doesn't have to do anything except assign and maybe validate some arguments.
(async () => {
const NPP = await NewPiecePlease.create(IPFS, OrbitDB);
console.log(NPP.piecesDb.id);
// This will give 'undefined' error
})();

Categories