Can not return from a function - javascript

I have a function that looks like following
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId, // set here the topicId which you want listen for
OnError: e => {
// react to error message (optional)
console.log("error: ", e);
},
OnServiceStateChange: e => {
if (e.ConnectedAdvisers > 0) {
// there are advisers online for given topicId
console.log("studio available");
return true;
} else {
console.log("studio not available");
return false;
}
}
});
serviceInfo.connect(serviceUrl, serviceId);
};
however the return statements don't return anything when I use the function in the following manner
useEffect(() => {
const agent = checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
);
// console.log("studio available is: ", agent);
}, []);
the console.log massages appear but the return statement is undefined.
any help would be appreciated.

You can not return from a callback function, as it is running asynchronously and you are not waiting for it to have a result ready.
You can however make the function itself async by returning a Promise instead of the actual result and wait until the Promise has a result ready (e.g. it is resolved):
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
return new Promise((resolve, reject) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId, // set here the topicId which you want listen for
OnError: e => {
// react to error message (optional)
console.log("error: ", e);
reject(); // reject on failure
},
OnServiceStateChange: e => {
if (e.ConnectedAdvisers > 0) {
// there are advisers online for given topicId
console.log("studio available");
resolve(true); // resolve instead of return
} else {
console.log("studio not available");
resolve(false);
}
}
});
serviceInfo.connect(serviceUrl, serviceId);
})
};
useEffect(() => {
checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
).then((agent) => { // then callback is called when the promise resolved
console.log("studio available is: ", agent);
}).catch(error => { // catch is called when promise got rejected
console.log('An error happened');
});
}, []);

The function servceInfo.OnServiceStateChange is a function into the object (seems to be an event).
I'd suggest declaring a variable on the checkForAvailableAgent like connected and change it's value when the event is called.
Then access it using checkForAvailableAgent.connected.

A version with async/await and try/catch
export const checkForAvailableAgent = (topicId, serviceUrl, serviceId) => {
return new Promise((resolve, reject) => {
const serviceInfo = new window.adiaLive.ServiceInfo({
topicId: topicId,
OnError: reject,
OnServiceStateChange: e => resolve(e.ConnectedAdvisers > 0)
});
serviceInfo.connect(serviceUrl, serviceId);
})
};
useEffect(() => {
(async () => {
try {
const isAvailable = await checkForAvailableAgent(
`sales_${i18n.language}`,
"https://linktoserviceurl",
"serviceid"
);
// console.log("Result", isAvailable)
} catch(e) {
console.error(e)
}
})()
// console.log("studio available is: ", agent);
}, []);

There are 2 possible reasons
you are not returning anything from checkForAvailableAgent.
After returning from the checkForAvailableAgent, it might be asynchronous function. You can use async & await.

Related

Changing script from request to axios - log pending promise

I'd like some help please as I'm quite new in node.js and working with node packages.
I'm having the following script which makes a GET http request running on node using request which is deprecated now
const foo = (bar, callback) => {
const url = 'https://some.api.com?key=abc123';
request({url: url, json: true}, (error, response) => {
if (error) {
callback('Oops, there is an error!', undefined);
} else if(response.body.foobarArray.length === 0) {
callback('No data found', undefined);
} else {
callback(undefined, {
foobar1: response.body.foobar1,
foobar2: response.body.foobar2,
})
}
});
}
console.log(foo('Hello')); // this logs {foobar1: 'Hello', foobar2: 'World'}
I'm trying to rewrite it using axios instead, so this is my code
const foo = async (bar) => {
const url = 'https://some.api.com?key=abc123';
try {
const response = await axios.get(url);
if (response.body.foobarArray.length === 0) {
return 'No data found';
} else {
return {
foobar1: response.body.foobar1,
foobar2: response.body.foobar2,
};
}
} catch (error) {
return 'Ooops! Something went wrong :(';
}
};
console.log(foo('Hello')); // This logs `Promise { <pending> }`
I'm not sure what I'm doing wrong here as I'm not very familiar how promises work exactly, but how can I fix this?
const foo = async (bar) => {
const url = 'https://some.api.com?key=abc123';
try {
return await axios.get(url).then(response => {
return new Promise((resolve, reject) => {
if (response.body.foobarArray.length === 0) {
return reject('No data found');
} else {
return resolve({
foobar1: response.body.foobar1,
foobar2: response.body.foobar2,
});
}
})
}).catch(err => {
return Promise.reject(err);
});
} catch (error) {
// return 'Ooops! Something went wrong :(';
return Promise.reject(`an error occurred : ${error}`);
}
};
foo('hello').then(result => {
console.log(result);
}).catch(err => {
console.log(`error ! : ${err}`);
});
async functions returns a promise. async functions use an implicit Promise to return its result. Even if you don't return a promise explicitly async function makes sure that your code is passed through a promise
as you are using axios asynchronous , it's response is a promise which must be handled inside .then().catch() functions .
if no error occurs you can access the response inside your .then() , else you will have access to your error on .catch()
inside your .then() you can now do what you want with data , returning a new Promise , using resolve() for success and reject() for failure .
You have 2 options here:
Option 1
Any async function returns a Promise (behind the scenes) so:
foo('Hello').then(console.log).error(console.error);
Option 2
You need to await for the result of foo function but, at the moment, you can't use await out of function scope level. So:
async function main() {
try {
const result = await foo('Hello');
console.log(result);
} catch (err) {
console.error(err);
}
}
main();
In future Node.js releases, using await at global scope will be allowed.

Catch block not firing all statements

My goal is to test my function: fetchStats
Expected Results: It console.logs the error and a failure message. and sets _isFetching to false.
Actual Results: The error is caught but console.logs not fired and isFetching is not set.
Code below:
fetchStats:
fetchStats() {
this._isFetching = true;
// fetch stats after building url and replacing invalid characters
return new Promise(async (resolve, reject) => {
await API.fetchStats(this.rsn)
.then(jres => {
this.skills = jres.main.skills;
this._isFetching = false;
resolve('success');
})
.catch(err => {
console.log(err);
console.log('error retreiving stats');
this._isFetching = false;
reject('Failed to retreive stats');
})
.finally(() => {
this._isFetching = false;
});
});
}
The test:
it("catches thrown errors", () => {
this.apiFetchStats.throws(new Error());
const player = Player.fromJSON(
JSON.stringify({
rsn: 'rsn',
skills: {
overall: { level: 2000 },
attack: { attack: {} },
defence: { defence: {} },
},
})
);
sinon.spy(console, "log");
player.fetchStats();
expect(this.apiFetchStats).to.be.calledOnce;
expect(this.apiFetchStats).to.have.thrown();
expect(console.log).to.have.been.calledTwice;
expect(player.isFetching()).to.be.false;
console.log.restore();
});
});
The first two expects pass fine, signalling that it has been called and did throw so it should be in catch block but console.log was not called and isFetching was not set. Does anyone have an idea on what is going wrong?
Resolved by adding expectations to .then of player.fetchStats like so:
player.fetchStats().then(() => {
expect(this.apiFetchStats).to.be.calledOnce;
expect(this.apiFetchStats).to.have.thrown();
expect(console.log).to.have.been.calledTwice;
expect(player.isFetching()).to.be.false;
})
.finally(() => { console.log.restore(); });

How to resolve promises and catch an error

I am trying to user webgazer.js where my code basically checks to see whether the webgazer is initialized and when it is initialized it resolves a promise which dispatches an action. This works however if for example there is no webcam I need to throw an error. The error in my code never gets called.
Here is my code
export function detectJsCamera() {
return async(dispatch) => {
dispatch({type: types.JS_DETECTING_CAMERA});
try {
await detectCamera();
await dispatch({type: types.JS_CAMERA_DETECTED});
} catch (error) {
await dispatch({type: types.CAMERA_DETECTION_FAILED, error: error.message});
throw error;
// this.props.history.push('/setup/positioning')
};
}
}
const detectCamera = () => new Promise((resolve, reject) => {
const checkIfReady = () => {
if (webgazer.isReady()) {
resolve('success');
} else {
console.log('called')
setTimeout(checkIfReady, 100);
}
}
setTimeout(checkIfReady,100);
});
You will need to reject in order to throw an exception like below
const detectCamera = () => new Promise((resolve, reject) => {
const checkIfReady = () => {
if (webgazer.isReady()) {
resolve('success');
} else {
console.log('called');
reject("some error");
}
}
setTimeout(checkIfReady,100);
});
You need to call reject() in your detectCamera method when your webgazer is not initialised then it would be caught in your catch block in detectJsCamera method.

Promise chaining with functions

I am trying to use promises for a small new project. But I have some problems with understanding how I could organize my code better, with functions. Problem is, I want my functions to handle things, and my main code to handle other things. So let's see this code:
function doSomething (isGoingToResolve = true) {
return new Promise((resolve, reject) => {
if (isGoingToResolve) {
resolve("something")
} else {
reject("something else")
}
}).then(response => {
console.log("in my function",response)
}).catch(error => {
console.log("in my function",error)
})
}
doSomething().then(response => {
console.log("in my main call", response)
})
With this code, the console will log in my function something and in my main call undefined: https://jsfiddle.net/hkacvw2g/
So I found two solutions to solve my problem, but I just don't like them:
The first one is to create a variable, use a .then() on the variable, and then return the variable (https://jsfiddle.net/hkacvw2g/1/):
function doSomething (isGoingToResolve = true) {
let promise = new Promise((resolve, reject) => {
if (isGoingToResolve) {
resolve("something")
} else {
reject("something else")
}
})
promise.then(response => {
console.log("in my function",response)
}).catch(error => {
console.log("in my function",error)
})
return promise
}
And the second solution (https://jsfiddle.net/hkacvw2g/2/):
function doSomething (isGoingToResolve = true) {
return new Promise((resolve, reject) => {
if (isGoingToResolve) {
resolve("something")
} else {
reject("something else")
}
}).then(response => {
console.log("in my function",response)
return new Promise(resolve => {
resolve(response)
})
}).catch(error => {
console.log("in my function",error)
return new Promise((resolve,reject) => {
reject(error)
})
})
}
Am I missing a better solution to solve my problem?
You can simply return the value (or re-throw the error) and it will be resolved in promise chain:
function doSomething (isGoingToResolve = true) {
return new Promise((resolve, reject) => {
if (isGoingToResolve) {
resolve("something")
} else {
reject("something else")
}
}).then(response => {
console.log("in my function",response)
return response;
}).catch(error => {
console.log("in my function",error)
throw error;
})
}
You might not want that throw error, it depends on how you want to handle your rejections. This way when you re-throw the error, you should catch it when calling the doSomething() method.
You can write a tap function, to tap into a promise chain, and do something while passing along the result, and a parallel tapCatch function, to tap into errors while rethrowing them:
const tap = fn => value => { fn(value); return value; };
const tapCatch = fn => reason => { fn(reason); throw reason; };
Now you can write your code as:
function doSomething(isGoingToResolve = true) {
(isGoingToResolve ?
Promise.resolve("something") :
Promise.reject("something else")
)
.then( tap (response => console.log("in my function", response)))
.catch(tapCatch(error => console.log("in my function", error)));
}
doSomething()
.then(response => console.log("in my main call", response));
However, actually your first solution is better. It reduces the risk of messing up the promise chain by inadvertently forgetting to, or not realizing that you have to, return the original value in then clauses, or rethrow in catch clauses which such clauses are meant only for logging purposes or other side-effects.
You could also pollute the Promise prototype with something like tap (we'll call it thenDo), making it a bit easier to use:
Promise.prototype.thenDo = function(ifFulfilled, ifRejected) {
return this.then(
value => { ifFulfilled(value); return value; },
reason => { ifRejected(reason); throw reason; });
};
Promise.prototype.catchDo = function(ifRejected) {
return this.catch(reason => { ifRejected(reason); throw reason; });
};
Now you can write
function doSomething(isGoingToResolve = true) {
(isGoingToResolve ?
Promise.resolve("something") :
Promise.reject("something else")
)
.thenDo (response => console.log("in my function", response))
.catchDo(error => console.log("in my function", error));
}

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