How to use local scope inside useEffect hook? - javascript

The following code works fine:
useEffect(() => {
const apiService = new APIClient('api address');
var topicCommentsReq = new GetTopicDetailsRequest();
topicCommentsReq.setTopicCid(Buffer.from("123", "hex"));
apiService.getTopicDetails(topicCommentsReq, {}, function (err, response) {
console.log(`response : ${JSON.stringify(response)}`);
});
}
I want to convert this callback style function to async/await style, so i did something like this:
const promisifyFn =
(fn: Function) =>
(...args: any[]) =>
new Promise((resolve, reject) => {
return fn(...args, (err: Error, data: any) => {
return err ? reject(err) : resolve(data);
});
});
useEffect(() => {
(async () => {
const apiService = new APIClient("api address");
const topicCommentsReq = new GetTopicDetailsRequest();
topicCommentsReq.setTopicCid(Buffer.from("123", "hex"));
const getTopicDetails = promisifyFn(apiService.getTopicDetails);
try {
const res = await getTopicDetails(topicCommentsReq, {});
console.log(`response : ${JSON.stringify(res)}`);
} catch (e) {
console.log(e);
}
})();
});
The following error message shows up:
TypeError: Cannot read properties of undefined (reading 'client_')
if (callback !== undefined) {
return this.client_.rpcCall(
this.hostname_ +
'/StelaPlatform.GRPC.API/GetTopicDetails',
request,
metadata || {},
this.methodDescriptorGetTopicDetails,
callback);
}
My guess is this problem related with the lcoal scope. When the function run inside async function, the this scope is different. But how should I resolve this issue in general?

The problem is in passing the function reference to apiService.getTopicDetails, it loses its this context.
In order to keep the prototype function in its prototype, you should use either an arrow function...
const getTopicDetails = promisifyFn((...args) =>
apiService.getTopicDetails(...args));
or Function.prototype.bind()
const getTopicDetails = promisifyFn(apiService.getTopicDetails.bind(apiService));

Related

Catching and returning an error from a callback that may be asynchronous

I have a function like so:
const x = y(callback);
x();
It may be called with a synchronous or asynchronous callback:
const a = y(() => 42);
const b = y(async () => Promise.resolve(42));
The function y should take a callback, that can be synchronous or asynchronous. Is it possible to catch a thrown error from either a synchronous or asynchronous callback?
I can't simply wrap the callback in a try/catch:
const y = (callback) => function () {
try {
return callback(...arguments);
}
catch (callbackError) {
throw callbackError;
}
};
because that doesn't catch the errors thrown from promises. I can't just chain a .catch onto the callback as it might not be a promise.
I've read that checking if a function is a promise before calling it may not trivial/may not be possible.
I can't call it to check if it returns a promise before chaining the catch onto it because if it throws, the catch would not have yet been chained onto the call
Is there a way to catch an error from a callback that may or may not be a promise?
Per the comments, I should have specified I don't want the function to return a promise. That is:
const a = y(() => { throw new Error(); })();
console.log(a instanceof Error);
const b = y(async () => { throw new Error(); })();
console.log(b instanceof Error);
const c = y(() => 42)();
console.log(c === 42);
const d = y(async () => Promise.resolve(42))();
console.log(d instanceof Promise);
should all be true. Simply put, I want to return an error or the result
An async function also emits a promise, you might consider to also wrap the async inside a promise, which on resolution emits the value
Realised that it's not necessary to chain the catch immediately! So the following abomination allows me to determine if it is a promise and handle the error accordingly:
const y = (error, callback) => function () {
try {
const returnValue = callback(...arguments);
if (returnValue instanceof Promise) {
return returnValue.catch((callbackError) => {
return callbackError;
});
}
else {
return returnValue;
}
}
catch (callbackError) {
return callbackError;
}
};
If you're wondering why this function is useful, here's an example of what I'm using it for. It modifies the error (chaining it with another error, see VError) before returning it:
const y = (error, callback) => function () {
try {
const returnValue = callback(...arguments);
if (returnValue instanceof Promise) {
return returnValue.catch((callbackError) => {
throw VError(error, callbackError);
});
}
else {
return returnValue;
}
}
catch (callbackError) {
throw VError(error, callbackError);
}
};
Note the return of the following as per the question:
const a = y(() => 42)(); // returns '42'
const b = y(async () => Promise.resolve(42))(); // returns 'Promise {42}'
const c = y(() => { throw new Error('Base'); })(); // throws VError
const d = y(async () => { throw new Error('Base'); })(); // throws VError
You can use async/await notation here.
Await will work with both synchronous and asnychronous functions.
Try to do:
async function init() {
const x = y( async () => await callback() );
try {
await x();
}
catch(error) {
/.../
}
}
and your function can be simplified now to:
const y = (callback) => function (...argument) {
return callback(...arguments);
};
Well you can do something like this by making all your code async... If I get your problem right.
UPD: understand the problem, here is my approach
(async () => {
// Run fn
const fn = callback => {
return new Promise(async (resolve, reject) => {
const res = await callback().catch(err => reject(err));
// Resolve
resolve(res);
});
};
const variousFNs = [
// Function 1
{
fn: () => 2+2
},
// Function 2 with error
{
fn: () => {
const x = 1;
x = 2;
return x;
}
},
// Promise Function 3
{
fn: () => Promise.resolve(8)
},
// Promise function 4 with error
{
fn: () => new Promise(async (resolve, reject) => reject('some promise error'))
}
];
//
//
// Run all your fns
for (let i = 0; i < variousFNs.length; i++) {
try {
const res = await variousFNs[i].fn();
console.log(res);
} catch(err) {
console.log(`Error catched: ${err}`);
}
}
})();

"this" key word is undefined while calling static method inside another static method (JavaScript)

I'm exporting/importing an empty array to implement MVC patern and I was able to successfully store data in it by calling the allMentors method on the Mentor class.
Secondly, I want to be DRY and get the data from the array and then use it to find a specific mentor but I'm getting an empty array.
I searched the internet and I followed the example of using this keyword and call the static method inside another but NodeJS is throwing an error that this is undefined.
ALL MENTORS method
class Mentor{
static async allMentors(req, res) {
try {
users.forEach(user => {
if(user.is_mentor === true) {
mentors.push(user);
}
})
const ObjKeyRename = (src, map) => {
const dst = {};
for (const key in src) {
if (key in map)
// rename key
dst[map[key]] = src[key];
else
// same key
dst[key] = src[key];
}
return dst;
};
const uniqueMentors = Array.from(new Set(mentors.map(m => m.id)))
.map(id => {
return new Promise((resolve, reject)=> {
const currMentor = mentors.find(m => m.id === id);
const modMentor = ObjKeyRename(currMentor, { "id": "mentorId" });
return resolve(modMentor);
})
})
Promise.all(uniqueMentors).then(output => {
output.forEach(async obj => {
await delete obj['password'];
})
return res
.status(200)
.json(new ResponseHandler(200, 'All Mentors', output, null).result());
})
} catch (err) {
return res
.status(500)
.json(new ResponseHandler(500, err.message, null).result());
}
}
static async singleMentor(req, res) {
const returnedMentors = this.allMentors;
console.log('THE RETURNED:',returnedMentors)
const theMentor = returnedMentors.find(u => u.mentorId === parseInt(req.params.mentorId));
try {
if (!theMentor) return res
.status(404)
.json(new ResponseHandler(404, `Mentor number ${req.params.mentorId} not found`, null).result());
return res
.status(200)
.json(new ResponseHandler(200, 'Your mentor', theMentor, null).result());
} catch (err) {
return res
.status(500)
.json(new ResponseHandler(500, err.message, null).result())
}
}
}
export default Mentor;
What am I doing wrong? I appreciate your continued help as a JS learner.
There are multiple problems with your code:
a static method needs to be called by Class (Mentor) not by Reference (this)
a async method needs to be awaited or called with a callback (.then())
So for example you have the class MentorHandler:
class Mentor {
constructor() {
}
static async singleMentor(req, res) {
// ... some more code
}
static async allMentors(req, res) {
// await the result of the method
// call by class
const returnedMentors = await Mentor.allMentors();
// ... some more code
}
}

Node.js - Mock result of a promise

I want to mock the result of a function within a node module so that i can run assertions.
Considering the following node module:
const doPostRequest = require('./doPostRequest.js').doPostRequest;
const normalizeSucessResult = require('./normalizer.js').normalizeSucessResult;
const normalizeErrorResult = require('./normalizer.js').normalizeErrorResult;
exports.doPost = (params, postData) => {
return doPostRequest(params, postData).then((res) => {
const normalizedSuccessResult = normalizeSucessResult(res);
return normalizedSuccessResult;
}).catch((err) => {
const normalizedErrorResult = normalizeErrorResult(err);
return normalizedErrorResult;
})
}
The function doPostRequest returns a promise. How can i fake the return value of this promise so that i can assert if normalizeSucessResult has been called?
So for i have tried:
const normalizeSucessResult = require('./normalizer.js');
const doPostRequest = require('./doPostRequests.js');
const doPost = require('./doPost.js');
it('runs a happy flow scenario', async () => {
let normalizeSucessResultStub = sinon.stub(normalizeSucessResult, 'normalizeSucessResult');
let postData = { body: 'Lorum ipsum' };
let params = { host: 'someUrl', port: 433, method: 'POST', path: '/' };
sinon.stub(doPostRequest, 'doPostRequest').resolves("some response data"); //Fake response from doPostRequest
return doPost.doPost(params, postData).then((res) => { //res should be equal to some response data
expect(normalizeSucessResultStub).to.have.been.calledOnce;
expect(normalizeSucessResultStub).to.have.been.with("some response data");
});
});
The doPostRequest module looks like this:
const https = require('https')
module.exports.doPostRequest = function (params, postData) {
return new Promise((resolve, reject) => {
const req = https.request(params, (res) => {
let body = []
res.on('data', (chunk) => {
body.push(chunk)
})
res.on('end', () => {
try {
body = JSON.parse(Buffer.concat(body).toString())
} catch (e) {
reject(e)
}
resolve(body)
})
})
req.on('error', (err) => {
reject(err)
})
if (postData) {
req.write(JSON.stringify(postData))
}
req.end()
})
}
You can use Promise.resolve to return a promise with any given value.
Promise.resolve(“hello world”);
For stub your func you need to do like this
sinon.stub({doPostRequest}, 'doPostRequest').resolves("some response data")
Okay, i figured it out. The function doPostRequest was loaded using require, on the top of the file using const doPostRequest = require('./doPostRequest.js').doPostRequest;
In order to mock the data that comes back from a function that is loaded using require i had to use a node module called mock-require. There are more modules that can take care of this (proxyquire is a populair one) but i picked mock-require (i did not have a specific reason for choosing mock-require).
For anyone else that is stuck with a similar problem, try mock-require to mock the respose from files that are loaded using require.

Jest: Testing a nested promise

I'm new to jest, and having trouble determining how to test results nested inside promises. Specifically:
myMethod: function (e) {
let self = this
self.resetErrors()
Parser.parseFile(this.form.uploadFile).then(res => {
const hasErrors = self.validFile(res)
if (!hasErrors) {
self.processFile(res)
}
})
}
I'd like to test to ensure that, assuming hasErrors is false, self.processFile fires. Here's my current (failing) best effort:
describe("if the provided data is valid", () => {
it('runs processFile', () => {
const mockProcessFile = jest.fn()
mockParser = jest.fn(() => {
new Promise((resolve, reject) => {
return ValidMockData
}).then((loanData) => {
expect(mockProcessFile).toBeCalled()
})
})
CsvParser.parseFile = mockParser
wrapper.vm.validFile = jest.fn(true)
wrapper.vm.processFile = mockProcessFile
wrapper.vm.store().resolve((data) => {
expect(mockProcessFile).toBeCalled()
})
})
})
At present I'm getting a Cannot read property 'then' of undefined error - which makes sense, but I'm not sure how exactly I'm supposed to crack into expectations inside of a then() call. Any thoughts appreciated
You need to store the Promise you create in the test so you can use await to let the test finishing after it was resolved:
describe("if the provided data is valid", async() => {
it('runs processFile', () => {
const mockProcessFile = jest.fn()
const p = Promise.resolve(ValidMockData)
CsvParser.parseFile = jest.fn(() => p)
wrapper.vm.validFile = jest.fn(true)
wrapper.vm.processFile = mockProcessFile
wrapper.vm.store()
await p
expect(mockProcessFile).toBeCalled()
expect(mockProcessFile).toBeCalled()
})
})

Is it safe to resolve a promise multiple times?

I have an i18n service in my application which contains the following code:
var i18nService = function() {
this.ensureLocaleIsLoaded = function() {
if( !this.existingPromise ) {
this.existingPromise = $q.defer();
var deferred = this.existingPromise;
var userLanguage = $( "body" ).data( "language" );
this.userLanguage = userLanguage;
console.log( "Loading locale '" + userLanguage + "' from server..." );
$http( { method:"get", url:"/i18n/" + userLanguage, cache:true } ).success( function( translations ) {
$rootScope.i18n = translations;
deferred.resolve( $rootScope.i18n );
} );
}
if( $rootScope.i18n ) {
this.existingPromise.resolve( $rootScope.i18n );
}
return this.existingPromise.promise;
};
The idea is that the user would call ensureLocaleIsLoaded and wait for the promise to be resolved. But given that the purpose of the function is to only ensure that the locale is loaded, it would be perfectly fine for the user to invoke it several times.
I'm currently just storing a single promise and resolve it if the user calls the function again after the locale has been successfully retrieved from the server.
From what I can tell, this is working as intended, but I'm wondering if this is a proper approach.
As I understand promises at present, this should be 100% fine. The only thing to understand is that once resolved (or rejected), that is it for a defered object - it is done.
If you call then(...) on its promise again, you immediately get the (first) resolved/rejected result.
Additional calls to resolve() will not have any effect.
Below is an executable snippet that covers those use cases:
var p = new Promise((resolve, reject) => {
resolve(1);
reject(2);
resolve(3);
});
p.then(x => console.log('resolved to ' + x))
.catch(x => console.log('never called ' + x));
p.then(x => console.log('one more ' + x));
p.then(x => console.log('two more ' + x));
p.then(x => console.log('three more ' + x));
I faced the same thing a while ago, indeed a promise can be only resolved once, another tries will do nothing (no error, no warning, no then invocation).
I decided to work it around like this:
getUsers(users => showThem(users));
getUsers(callback){
callback(getCachedUsers())
api.getUsers().then(users => callback(users))
}
just pass your function as a callback and invoke it as many times you wish! Hope that makes sense.
There s no clear way to resolve promises multiple times because since it's resolved it's done. The better approach here is to use observer-observable pattern for example i wrote following code that observes socket client event. You can extend this code to met your need
const evokeObjectMethodWithArgs = (methodName, args) => (src) => src[methodName].apply(null, args);
const hasMethodName = (name) => (target = {}) => typeof target[name] === 'function';
const Observable = function (fn) {
const subscribers = [];
this.subscribe = subscribers.push.bind(subscribers);
const observer = {
next: (...args) => subscribers.filter(hasMethodName('next')).forEach(evokeObjectMethodWithArgs('next', args))
};
setTimeout(() => {
try {
fn(observer);
} catch (e) {
subscribers.filter(hasMethodName('error')).forEach(evokeObjectMethodWithArgs('error', e));
}
});
};
const fromEvent = (target, eventName) => new Observable((obs) => target.on(eventName, obs.next));
fromEvent(client, 'document:save').subscribe({
async next(document, docName) {
await writeFilePromise(resolve(dataDir, `${docName}`), document);
client.emit('document:save', document);
}
});
If you need to change the return value of promise, simply return new value in then and chain next then/catch on it
var p1 = new Promise((resolve, reject) => { resolve(1) });
var p2 = p1.then(v => {
console.log("First then, value is", v);
return 2;
});
p2.then(v => {
console.log("Second then, value is", v);
});
You can write tests to confirm the behavior.
By running the following test you can conclude that
The resolve()/reject() call never throw error.
Once settled (rejected), the resolved value (rejected error) will be preserved
regardless of following resolve() or reject() calls.
You can also check my blog post for details.
/* eslint-disable prefer-promise-reject-errors */
const flipPromise = require('flip-promise').default
describe('promise', () => {
test('error catch with resolve', () => new Promise(async (rs, rj) => {
const getPromise = () => new Promise(resolve => {
try {
resolve()
} catch (err) {
rj('error caught in unexpected location')
}
})
try {
await getPromise()
throw new Error('error thrown out side')
} catch (e) {
rs('error caught in expected location')
}
}))
test('error catch with reject', () => new Promise(async (rs, rj) => {
const getPromise = () => new Promise((_resolve, reject) => {
try {
reject()
} catch (err) {
rj('error caught in unexpected location')
}
})
try {
await getPromise()
} catch (e) {
try {
throw new Error('error thrown out side')
} catch (e){
rs('error caught in expected location')
}
}
}))
test('await multiple times resolved promise', async () => {
const pr = Promise.resolve(1)
expect(await pr).toBe(1)
expect(await pr).toBe(1)
})
test('await multiple times rejected promise', async () => {
const pr = Promise.reject(1)
expect(await flipPromise(pr)).toBe(1)
expect(await flipPromise(pr)).toBe(1)
})
test('resolve multiple times', async () => {
const pr = new Promise(resolve => {
resolve(1)
resolve(2)
resolve(3)
})
expect(await pr).toBe(1)
})
test('resolve then reject', async () => {
const pr = new Promise((resolve, reject) => {
resolve(1)
resolve(2)
resolve(3)
reject(4)
})
expect(await pr).toBe(1)
})
test('reject multiple times', async () => {
const pr = new Promise((_resolve, reject) => {
reject(1)
reject(2)
reject(3)
})
expect(await flipPromise(pr)).toBe(1)
})
test('reject then resolve', async () => {
const pr = new Promise((resolve, reject) => {
reject(1)
reject(2)
reject(3)
resolve(4)
})
expect(await flipPromise(pr)).toBe(1)
})
test('constructor is not async', async () => {
let val
let val1
const pr = new Promise(resolve => {
val = 1
setTimeout(() => {
resolve()
val1 = 2
})
})
expect(val).toBe(1)
expect(val1).toBeUndefined()
await pr
expect(val).toBe(1)
expect(val1).toBe(2)
})
})
What you should do is put an ng-if on your main ng-outlet and show a loading spinner instead. Once your locale is loaded the you show the outlet and let the component hierarchy render. This way all of your application can assume that the locale is loaded and no checks are necessary.
No. It is not safe to resolve/reject promise multiple times. It is basically a bug, that is hard to catch, becasue it can be not always reproducible.
There is pattern that can be used to trace such issues in debug time. Great lecture on this topic: Ruben Bridgewater — Error handling: doing it right! (the part related to the question is around 40 min)
see github gist: reuse_promise.js
/*
reuse a promise for multiple resolve()s since promises only resolve once and then never again
*/
import React, { useEffect, useState } from 'react'
export default () => {
const [somePromise, setSomePromise] = useState(promiseCreator())
useEffect(() => {
somePromise.then(data => {
// do things here
setSomePromise(promiseCreator())
})
}, [somePromise])
}
const promiseCreator = () => {
return new Promise((resolve, reject) => {
// do things
resolve(/*data*/)
})
}

Categories