wait for result before proceeding - javascript

I'm using Google's reCaptcha V3 as part of my Angular(7) project.
I would like to wait for the Token's response before proceeding to the rest of the code and before checking whether the Token is validated or not.
declare var grecaptcha: any;
ngOnInit() {
this.validateCaptcha();
if(this.gToken)
{
...
}
}
gToken:string;
validateCaptcha()
{
let self = this;
grecaptcha.ready(function() {
grecaptcha.execute('reCAPTCHA_site_key', {action: 'homepage'}).then(function(token){
self.gToken = token;
});
});
}
The thing is that this.gToken is undefined because it doesn't wait to validateCaptcha to finish its job.
I have also tried async and await but it didn't help. perhaps I used it wrongly.

You can use Promise here.
You need to wait until token generated.
You can also use async / await instead of Promise.
ngOnInit() {
this.validateCaptcha().then(token => { // Here wait token generated
if(token) {
}
})
}
validateCaptcha() {
return new Promise((res, rej) => {
grecaptcha.ready(() => {
grecaptcha.execute('reCAPTCHA_site_key', {action:
'homepage'}).then((token) => {
return res(token);
})
})
})
}
Update example with async / await.
While using async / await the validateCaptcha method demonstrated above remains same (must return Promise)
async ngOnInit() { // async keyword added here
try {
let token = await this.validateCaptcha(); // await keyword added here
if(token) {
}
} catch(e) {
}
}

Why not have validateCaptcha() return a promise?
declare var grecaptcha: any;
ngOnInit() {
this.validateCaptcha().then(() => {
if(this.gToken)
{
...
}
});
}
gToken:string;
validateCaptcha()
{
let self = this;
return new Promise((resolve, reject) => {
grecaptcha.ready(function() {
grecaptcha.execute('reCAPTCHA_site_key', {action: 'homepage'}).then(function(token){
self.gToken = token;
resolve();
});
});
});
}

Related

Resolve promise after callback it's executed

I have the following code on which I am trying to block the execution of the method _saveAddress multiple time, so I made a promise for this method.
const [pressEventDisabled, setPressEventDisabled] = useState(false);
<TouchableOpacity style={style.button_container} activeOpacity={1} disabled={pressEventDisabled} onPress={async () => {setPressEventDisabled(true); await _saveAddress(); setPressEventDisabled(false);}} >
The problem is that I want to resolve the promise after the callback method it's executed. It's there any way to wait for the dispatch function to execute or to resolve the promise inside the callback method?
This is the method for saving the address:
const _saveAddress = () => new Promise(async (resolve) => {
var valid = _validate();
if (valid) {
const address = createAddressJson();
if (addressId) {
var addressIdProperty = {
id: addressId
};
const newAddress = Object.assign(addressIdProperty, address);
dispatch(editAddress(newAddress, _onAddressSaveEditCallback));
} else {
dispatch(addAddress(address, _onAddressSaveEditCallback));
}
} else {
//notify
notifyMessage(strings.fill_required_inputs_validation);
resolve();
}
});
This is the callback method:
const _onAddressSaveEditCallback = async (success: boolean, apiValidations: any, address ? : Address, ) => {
if (success) {
if (typeof callback == 'function') {
callback(address);
}
await Navigation.pop(componentId);
} else {
setDataValidations(apiValidations);
}
};
Just do exactly what you say in the title. Nothing more, nothing less:
if (addressId) {
var addressIdProperty = {id: addressId};
const newAddress = Object.assign(addressIdProperty, address);
dispatch(editAddress(newAddress, async (s,v,a) => {
await _onAddressSaveEditCallback(s,v,a);
resolve();
}));
} else {
dispatch(addAddress(address, async (s,v,a) => {
await _onAddressSaveEditCallback(s,v,a);
resolve();
}));
}
Of course, since you are passing async () => {} to addAddress instead of _onAddressSaveEditCallback you have to call _onAddressSaveEditCallback yourself since addAddress will be calling the async () => ...
But mixing promises and callbacks like this isn't great. It leads to weird looking and sometimes confusing code. A better solution is to promisify addAddress:
function addAddressPromise (address) {
return new Promise((resolve, reject) => {
addAddress(address, (success, validations, address) {
if (success) return resolve(address);
else reject(validations)
});
});
}
Now you can wait for addAddress:
const _saveAddress = async () => {
// Don't create new promise here, we do it in addAddress..
// ...
let result = await addAddressPromise(address);
dispatch(result);
await _onAddressSaveEditCallback();
// ...
}

JS Get data from a constructor with a Promise

I'm unable to get the data response from unsplashAxios within the GetPhoto constructor, at the moment it just says 'Promise { }'. Have I missed something blatantly obvious or does this need rethinking?
Attempt 1
class Unsplash {
constructor(path) {
return new Promise((resolve, reject) => {
unsplashAxios(path)
.then(response => {
resolve(response);
})
.catch(error => {
reject(error);
});
});
}
}
class GetPhoto {
constructor() {
console.log('Get Photo');
const unsplash = new Unsplash('/photos/PgHc0Ka1E0A');
console.log(unsplash)
// Console.log Response - Promise { <pending> }
}
}
Attempt 2
class Unsplash {
constructor(path) {
return unsplashAxios(path)
}
}
class GetPhoto {
constructor() {
const unsplash = new Unsplash('/photos/PgHc0Ka1E0A');
unsplash.then((response) => {
console.log(response)
}).catch((response) => {
console.log(response);
});
}
}
Attempt 3 - After #Klaycon I've rewrote the above and this seems to work. But feedback would be great (good or bad).
const unsplashAxios = require('./unsplashAxios');
// Class
class Unsplash {
// Constructor
constructor() {
this.unsplash = null;
}
// Method
getPhoto(id){
this.unsplash = unsplashAxios( `/photos/${id}`);
this.unsplash.then((response) => {
console.log(response)
}).catch((response) => {
console.log(response);
});
}
// Method
getCollection(id){
this.unsplash = unsplashAxios(`/collections/${id}/photos`);
this.unsplash.then((response) => {
console.log(response)
}).catch((response) => {
console.log(response);
});
}
}
// Instance
myUnsplash = new Unsplash();
// Method
myUnsplash.getPhoto('PgHc0Ka1E0A');
// Method
myUnsplash.getCollection('62890');
You return a Promise.
As #Bergi said in your comment, avoid to use them in a constructor.
Use :
unsplash.then((data) => {
//do something with data
});
to wait til the promise is resolved. You can read more about promises here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Since unsplashAxios is already returning a promise, you don't need to wrap it in another promise (you're resolving unsplashAxios's promise then putting that result into another Promise that would have to be resolved elsewhere. Changing your code to this should work:
constructor(path) {
unsplashAxios(path)
.then(response => {
//do something with the response
})
.catch(error => {
//do something with the error
}
}
}
I ended up rewriting and found the following to work as expected:
class Unsplash {
constructor(path) {
return unsplashAxios(path)
}
}
class GetPhoto {
constructor() {
const unsplash = new Unsplash('/photos/PgHc0Ka1E0A');
unsplash.then((response) => {
console.log(response)
}).catch((response) => {
console.log(response);
});
}
}

Calling an async function when a constructor() is present

I have an async function that generates a new token if a user's token expires. I copied and pasted the async function code into this project and the class and constructor were already built.
I want to call the async function but I'm not sure how/where to do it, especially with the constructor function present. I can see how and where the other async functions are called (i.e. var _token = await sessionGet(_tokenName)), but since the async function I want to call is the outermost async function (if that makes sense) I'm not sure where I'd call it.
Here's what I know (correct me if I'm wrong):
I can't use await outside an async function
It's a bad practice to have a constructor function return a Promise
My async function has to be async or else it'll "break the flow" of the rest of the code---async is used multiple times throughout
export default class {
constructor() {
this.setTokenVar();
// other function calls
}
setTokenVar() {
async function permissionToCallAPI() { // -------- this is what I want to call
const _tokenName = "tokenSet",
_RestHost = "https://.../.../api";
async function sessionGet(key) {
let stringValue = window.sessionStorage.getItem(key);
if (stringValue != null) {
try {
const {
value,
expirationDateStr
} = JSON.parse(stringValue);
if (value && expirationDateStr) {
let expirationDate = new Date(expirationDateStr);
if (expirationDate <= new Date()) {
throw "Expired Token";
}
const isValid = await isAuthorized(value.value);
if (isValid) {
console.log("Valid Token");
return value;
} else {
throw "Expired Token";
}
} // if (value && expirDateStr)
} catch (e) {
console.log(e);
window.sessionStorage.removeItem(key);
}
}
return null;
}
async function isAuthorized(key) {
let ret = false;
await axios.post(_RestHost + "/User/GenerateToken", null, {
withCredentials: true,
async: false,
headers: {
"accept": "application/json;odata=verbose",
"content-type": "application/json",
"Authorization": key
}
}).then(resp => {
ret = true;
})
.catch(err => {
console.log("Failed Authentication.");
});
return ret;
}
// add into session
function sessionSet(key, value, expirationInMin) {
if (!expirationInMin) {
expirationInMin = 59;
}
var expirationDate = new Date(
new Date().getTime() + 60000 * expirationInMin
);
var newValue = {
value: value,
expirationDateStr: expirationDate.toISOString()
};
window.sessionStorage.setItem(key, JSON.stringify(newValue));
}
var _token = await sessionGet(_tokenName);
if (_token == null) {
let request = axios
.get(_RestHost + "/User/GenerateToken", {
withCredentials: true
});
return request
.then(response => {
const authToken = response.data.AuthToken;
sessionSet(_tokenName, authToken);
return authToken;
})
.catch(err => {
throw err;
});
} else {
return _token;
}
} // permToCallAPI
} // setTokenVar()
}
Doing anything but setting variables in the constructor is considered an anti-pattern.
The simplest solution is to make the caller responsible for calling obj.setTokenVar() which could then be an async function.
Since the object's state may be inconsistent before that method is called, you could create a factory async function to guarantee that the object is fully initialized before letting callers use it.
export async createThing() {
const obj = new Thing();
await obj.setTokenVar();
return obj;
}
Side Note
async can be called from non async functions, but you cannot use await on it, you must use then on the Promise that all async functions return.
function doSomething() {
createThing().then(thing => console.log(thing))
}
function async doSomething() {
console.log(await createThing())
}

Dispatch async action

By changing my action to async I am not able to dispatch it. Whenever I make the dispatch it enters the cath with the value false. Here is my page where I dispatch the action from mounted hook (I tried created too)
mounted () {
this.$store.dispatch('productById', this.$route.params['id']).then((response) => {
this.product = response
})
.catch(err => {
console.log(err)
})
}
And this is my action
async productById ({commit}, payload) {
const AuthStr = await getAdminOrRespondentAuth()
return new Promise((resolve, reject) => {
commit(PRODUCT_BY_ID)
axios.get(`${API_BASE}/products/${payload}`, {
params: {
origin: '1'
},
transformRequest: [function (data, headers) {
delete headers.common.Authorization
headers.Authorization = AuthStr
return data
}],
paramsSerializer: params => parseParams(params)
}).then(response => {
if (response.status === 200) {
commit(PRODUCT_BY_ID_SUCCESS, response.data)
resolve(response.data)
} else {
reject(response)
}
})
.catch(err => {
if (err.response.data.idStatus === 1) {
commit(PRODUCT_BY_ID_SUCCESS, err.response.data.data)
reject(err)
}
})
})
}
When the Vue enters mounted hook it "dispatch the action" and goes straight into the catch block not calling my action. My action is not executed.
If I change my action to sync, everything works normally. I need this action to be async because getAdminOrRespondentAuth function thats call oidc async method to recover user.
What I'm doing wrong?
#Samurai8 is right. Thanks for the help. My getAdminOrRespondentAuth was not returning a promise correctly. After correcting the error of the function everything came back to work. This is the function that generate error discribed:
async function getAdminOrRespondentAuth () {
let mgr = new Mgr()
var adminToken = await mgr.getToken()
if (adminToken !== false) {
return 'Bearer '.concat(adminToken)
} else {
let usrToken = localStorage.getItem('user-token')
return 'Bearer '.concat(usrToken)
}
}
Here is the function that works:
async function getAdminOrRespondentAuth () {
var adminToken = ''
return new Promise(async (resolve, reject) => {
let mgr = new Mgr()
try {
adminToken = await mgr.getToken()
} catch (error) {
adminToken = error
}
if (adminToken !== false) {
resolve('Bearer '.concat(adminToken))
} else {
let usrToken = localStorage.getItem('user-token')
if (usrToken !== null) {
resolve('Bearer '.concat(usrToken))
} else {
resolve('')
}
}
})
}

There is a function with a promise. Inside this function I call this function again (recursion). How to wait till recursed promise is resolved?

There are 2 functions that I need to run one-by-one: getUserPlaylists (receives Playlists) and getPlaylistTracks (receives Tracks for provided Playlist).
One response can have up to 50 tracks, so I need to use PageToken if I want to get the rest tracks. The problem is that I can not make a recursive function getPlaylistTracks to wait till the recursion is done.
function getPlaylistsWithTracks () {
return new Promise((resolve, reject) => {
getUserPlaylists()
.then(function (playlists) {
playlists.forEach(
async function (playlistObj) {
await getPlaylistTracks(playlistObj).then(function (tracks) {
playlistObj['tracks'] = tracks
})
})
console.log('resolve')
resolve(playlists)
})
})
}
function getPlaylistTracks (playlistObj, pageToken) {
return new Promise((resolve, reject) => {
let playlistTracks = []
let requestOptions = {
'playlistId': playlistObj['youtubePlaylistId'],
'maxResults': '50',
'part': 'snippet'
}
if (pageToken) {
console.log('pageToken:', pageToken)
requestOptions.pageToken = pageToken
}
let request = gapi.client.youtube.playlistItems.list(requestOptions)
request.execute(function (response) {
response['items'].forEach(function (responceObj) {
let youtubeTrackTitle = responceObj.snippet.title
if (youtubeTrackTitle !== 'Deleted video') {
let youtubeTrackId = responceObj.snippet.resourceId.videoId
playlistTracks.push({
youtubePlaylistId: playlistObj.playlistId,
youtubePlaylistTitle: playlistObj.playlistTitle,
youtubeTrackId: youtubeTrackId,
youtubeTrackTitle: youtubeTrackTitle,
})
}
})
// Here I need to wait a bit
if (response.result['nextPageToken']) {
getPlaylistTracks(playlistObj, response.result['nextPageToken'])
.then(function (nextPageTracks) {
playlistTracks = playlistTracks.concat(nextPageTracks)
})
}
})
resolve(playlistTracks)
})
}
getPlaylistsWithTracks()
In my case in console I see the next:
> resolve
> pageToken: 123
> pageToken: 345
but, I want to see resolve the last.
How to wait till recursion is executed?
Avoid the Promise constructor antipattern, and (don't) use forEach with async functions properly.
Furthermore, there is nothing special about recursion. It's like any other promise-returning function call that you would want to wait for - put it in your then chain or await it. (The latter is considerably easier).
async function getPlaylistsWithTracks() {
const playlists = await getUserPlaylists();
for (const playlistObj of playlists) {
const tracks = await getPlaylistTracks(playlistObj);
playlistObj.tracks = tracks;
}
console.log('resolve')
return playlists;
}
async function getPlaylistTracks(playlistObj, pageToken) {
let playlistTracks = []
let requestOptions = {
'playlistId': playlistObj['youtubePlaylistId'],
'maxResults': '50',
'part': 'snippet'
}
if (pageToken) {
console.log('pageToken:', pageToken)
requestOptions.pageToken = pageToken
}
let request = gapi.client.youtube.playlistItems.list(requestOptions)
const response = await new Promise((resolve, reject) => {
request.execute(resolve); // are you sure this doesn't error?
});
response['items'].forEach(function (responceObj) {
let youtubeTrackTitle = responceObj.snippet.title
if (youtubeTrackTitle !== 'Deleted video') {
let youtubeTrackId = responceObj.snippet.resourceId.videoId
playlistTracks.push({
youtubePlaylistId: playlistObj.playlistId,
youtubePlaylistTitle: playlistObj.playlistTitle,
youtubeTrackId: youtubeTrackId,
youtubeTrackTitle: youtubeTrackTitle,
})
}
})
if (response.result['nextPageToken']) {
const nextPageTracks = await getPlaylistTracks(playlistObj, response.result['nextPageToken']);
playlistTracks = playlistTracks.concat(nextPageTracks);
}
return playlistTracks;
}

Categories

Resources