Async ES2017 Constructor - javascript

What is the most up to date method to ensure that some asynchronous code completes in a class constructor before that class is subsequently used?
Specifically, how would an API client class retrieve an access token before allowing more method calls, as shown below?
class API_Client {
constructor(...) {
# Below should 'block' other method calls until token is assigned
this.login().then(res => {
this.token = res.data.token;
});
}
async login() {
return makeRequest(...) # <-- Promise which returns access token data
}
}
const client = new API_Client(...);
client.someAuthOnlyMethod() # <-- Should only happen after the `login` method completes.
I found older answers, yet couldn't quite understand how to solve the problem posed in the first comment left on the linked answer.

The most up-to-date method is still not to put any asynchronous stuff in the constructor. In your specific case, that's
class API_Client {
constructor(token) {
this.token = token;
}
static async createLoggedIn(…) {
const res = await makeRequest(...) # <-- Promise which returns access token data
return new this(res.data.token);
}
}
const client = await API_Client.createLoggedIn(…);
client.someAuthOnlyMethod()

You could store the token as a promise:
class API_Client {
constructor(...) {
# Below should 'block' other method calls until token is assigned
this.token = this.login()
.then(res => res.data.token)
}
async someAuthOnlyMethod() {
let token = await this.token;
//...continue
}
async login() {
return makeRequest(...) # <-- Promise which returns access token data
}
}
const client = new API_Client(...);
client.someAuthOnlyMethod() # <-- Should only happen after the `login` method completes.

You shouldn't be calling any asynchronous code from a constructor to begin with. In the case above, your makeRequest function would worry about the login token.
There is also no real value in a class in this case. You should just export a series of functions to make the API calls.

Related

Waiting for the result of a function called from somewhere else

I have a Node server where I want to integrate Paypal. I have one module that is supposed to return the access token from paypal. This module has one async function and looks something like this:
let token: string | null = null;
export async function getToken() {
if (token === null) {
token = await requestTokenFromPaypal()
}
return token;
}
export function resetToken() { token = null }
main:
getToken()
getToken()
getToken()
// -> I want all calls to wait until the first request for the token is finished and then resolve to the token
// -> Currently the token will be requested three times
The resetToken function is called if the other requests yield 401 and the token is invalid. So a new token is needed. But now every call to the getToken will result in a token request until the first request is finished and saved to token.
So how can I wait for the result from another function that was called from a different piece of code? I have already thought about saving the promise or something like that but I cant wrap my head around it.
I also tried to google it but only get the standart async code questions. Please refer me if you find other similar questions.
Instead of checking whether the token is available, you could check whether the request for the token has already been started.
You can do this by checking on the tokenPromise that is basically just a wrapper around the token.
let tokenPromise = null
export async function getToken() {
if (!tokenPromise) {
tokenPromise = requestTokenFromPaypal()
}
return tokenPromise
}
export function resetToken() { tokenPromise = null }
In case you wonder, directly returning the tokenPromise without awaiting for it will actually have the same effect as awaiting for it and then returning the token.
Simply return a promise, and .then it.
let token: string | null = null;
export async function getToken() {
if (token === null) {
return requestTokenFromPaypal() // this function should return a promise
/* (node-fetch would be the easiest way to do that, but whatever works) */
}
return Promise.resolve(token);
}
export function resetToken() { token = null }
//later, somewhere else in your code
getToken().then(function(token) {
//do something with 'token'
});

Alternative for `return await …` for the cases when waiting for some result which must be returned

I know that ESLint/TSLint rulest cannot be "right" for 100% of situations. However I need to decide which rules I really don't need.
In ElectonJS, it's not recommended to use Node.js modules in Renderer Process. Instead, Renderer process should send request to Main Process and listen for the response. Below TypeScript class takes care about this routine. (I hope my variable names make code does not need comments, but those are hard to understand, please let me to know in comments)
import { ipcRenderer as IpcRenderer } from "electron";
import InterProcessDataTransferProtocol from "#ProjectInitializer:Root/InterProcessDataTransferProtocol";
import CheckingPathForWriteAccess = InterProcessDataTransferProtocol.CheckingPathForWriteAccess;
import MainProcessEvents = InterProcessDataTransferProtocol.MainProcessEvents;
import Timeout = NodeJS.Timeout;
export default abstract class InterProcessFacilitator {
private static readonly IS_PATH_WRITABLE__RESPONSE_WAITING_PERIOD__MILLISECONDS: number = 10000;
public static async requestCheckingPathForAccessToWrite(targetPath: string): Promise<boolean> {
IpcRenderer.send(
InterProcessDataTransferProtocol.RendererProcessEvents.checkingPathForWriteAccessRequestSent,
{ targetPath }
);
const responseWaitingTimeout: Timeout = setTimeout(
() => { throw new Error("No response from Main Process"); },
InterProcessFacilitator.IS_PATH_WRITABLE__RESPONSE_WAITING_PERIOD__MILLISECONDS
);
return await new Promise<boolean>((resolve: (isWritable: boolean) => void): void => {
IpcRenderer.on(
MainProcessEvents.checkingPathForWriteAccessDone,
(_event: Electron.Event, responsePayload: CheckingPathForWriteAccess.ResponsePayload) =>
{
clearTimeout(responseWaitingTimeout);
if (responsePayload.targetPath === targetPath) {
resolve(responsePayload.isWritable);
}
});
});
}
}
Currently, method requestCheckingPathForAccessToWrite violates no-return-await, the ESLint rules. However it could be used as:
async function checkTheDefaultPathForWrightPermission(): Promise<void> {
try {
const pickedPathIsWritable: boolean = await InterProcessFacilitator
.requestCheckingPathForAccessToWrite(DEFAULT_PATH);
pickedPathIsWritable ?
this.relatedStoreModule.reportAboutUnableToWriteToDirectoryErrorResolution() :
this.relatedStoreModule.reportAboutUnableToWriteToDirectoryErrorOccurrence();
} catch (error) {
console.error(`unable to check wright permission for path ${DEFAULT_PATH}`);
console.error(error);
}
}
From ESLint documentation:
Inside an async function, return await is seldom useful. Since the
return value of an async function is always wrapped in
Promise.resolve, return await doesn’t actually do anything except add
extra time before the overarching Promise resolves or rejects. The
only valid exception is if return await is used in a try/catch
statement to catch errors from another Promise-based function.
Can you criticize either my solution or this ESLint rule, and in first case, suggest the refactoring?

Unexpected token when use Public keyword in javascript

As per Apollo Documentation adding Custom Directive is as follow:
class isAuth extends SchemaDirectiveVisitor {
// field defination for resolving
// directive.
public visitFieldDefinition(field) {
// overspreading.
const { resolve = defaultFieldResolver } = field;
// resolve field.
field.resolve = async function(__, Context) {
// check if Context
// contain's user object
// which was resolved by jwt.
if(!Context.user){
throw new Error('User not authenticated');
}
// resolve return awaited resolve call.
return await resolve.apply(this, __);
};
}
}
but using this will going to result in an unexpected token error.
class isAuth extends SchemaDirectiveVisitor {
// field defination for resolving
// directive.
visitFieldDefinition(field) { <---- Removed Public keyword
// overspreading.
const { resolve = defaultFieldResolver } = field;
// resolve field.
field.resolve = async function(__, Context) {
// check if Context
// contain's user object
// which was resolved by jwt.
if(!Context.user){
throw new Error('User not authenticated');
}
// resolve return awaited resolve call.
return await resolve.apply(this, __);
};
}
}
this will going to work but not for apollo Package.
please note: Apollo is graphql library
public keyword is present in JavaScript but it is reserved for future use.
Therefore as of 2019, any JavaScript parser will always give you unexpected token error.

Typescript error when returning Response and accessing member

This code works fine:
const { response } = yield call(makeGetRequest, requestUrl);
return response.data;
However, this does not:
function makeCall() {
...
const { response } = yield call(makeGetRequest, requestUrl);
return response;
}
function returnData() {
const response = makeCall();
return response.data;
}
The typescript error is in the return response.data line:
Property data does not exist on type.
I assume this is because it is treating response as an any, but I have no idea how I would give the method the right info about what type of object response is. I tried typeof response and just got Object.
is makeCall meant to be a generator function? It has to be if you're using using yield, so presumably you meant to type function* makeCall(). But if it's a generator, then when you do this:
function returnData() {
const response = makeCall();
return response.data;
}
... the return value of makeCall() is an iterator object. Iterators have no .data property, which is why typescript is giving you an error.
As for how to correct this, i'll need more information about what you're trying to do. Is this redux-saga code? If so, you shouldn't be calling sagas yourself, but instead you have it listen to actions (eg, with take, takeEvery, or takeLatest) and redux saga will run the saga when the actions occur.
If you want to call a saga from another saga you can, but you'll either need to use yield*:
function otherSaga*() {
const response = yield* makeCall()
console.log(response.data);
}
Or the call effect:
function otherSaga*() {
const response = yield call(makeCall);
console.log(response.data);
}

calling an async function in the constructor.

getUser is an async function? if it is going to take longer time to resolve? is it going to always return the right value in my someotherclass.
class IdpServer {
constructor() {
this._settings = {
// some identity server settings.
};
this.userManager = new UserManager(this._settings);
this.getUser();
}
async getUser() {
this.user = await this.userManager.getUser();
}
isLoggedIn() {
return this.user != null && !this.user.expired;
}
}
let idpServer = new IdpServer();
export default idpServer;
// another class
// import IdpServer from '...'
class SomeOtherClass {
constructor() {
console.log(IdpServer.isLoggedIn());
}
}
This is a problem that is related to this popular question.
Once a code is asynchronous, it cannot be used in synchronous manner. If the use of raw promises is unwanted, all control flow should be performed with async functions.
The problem here is that getUser provides a promise of user data, not user data itself. A promise is lost in constructor, and this is antipattern.
One way to solve the problem is to provide initialization promise for IdpServer, while the rest of API will be synchronous:
class IdpServer {
constructor() {
...
this.initializationPromise = this.getUser();
}
async getUser() {
this.user = await this.userManager.getUser();
}
isLoggedIn() {
return this.user != null && !this.user.expired;
}
}
// inside async function
await idpServer.initializationPromise;
idpServer.isLoggedIn();
Depending on how the application works, IdpServer.initializationPromise can be handled on application initialization to guarantee that all units that depend on IdpServer won't be initialized until it's ready.
Another way is to make IdpServer entirely asynchronous:
class IdpServer {
constructor() {
...
this.user = this.getUser(); // a promise of user data
}
async getUser() {
return this.userManager.getUser();
}
async isLoggedIn() {
const user = await this.user;
return user != null && !user.expired;
}
}
// inside async function
await idpServer.isLoggedIn();
It's expected that all units that depend on it will also have asynchronous API.

Categories

Resources