Promise resolving once - javascript

Promise in my code is resolving only at first execution, it is simple form submission with reCAPTCHA verification. From debugging I know the browser interpreter reaches line with await captchaVerification() and stops there. First execution works without any errors.
contactForm.addEventListener('submit', async (e) => {
e.preventDefault();
await captchaVerification()
const data = new FormData(contactForm)
_alert.innerHTML = 'Sending message...'
_alert.setAttribute('class', `alert show`);
ajax(contactForm.method, contactForm.action, data)
.then(([r, t]) => outcome(r ? r = 'success' : r = 'error', t))
.catch(e => outcome('error', e))
});
Full context on hastebin: https://hastebin.com/oyuvoyeluv.js

From the link you posted I can see that in you captchaVerification function, you check if a captcha has previously been rendered, if not you render and resolve or reject the promise. The problem is you never resolve or reject if isRendered is true.
function captchaVerification() {
return new Promise((resolve, reject) => {
captcha.style.display = 'block';
const verifyCallback = (response) => {
if (response) {
captcha.style.display = 'none'
grecaptcha.reset()
return resolve(response)
} else {
grecaptcha.reset()
return reject('Invalid captcha verification.')
}
}
// The problem is here as you do nothing if the captcha was
// previously rendered.
if (!isRendered) {
grecaptcha.render('g-recaptcha', {
'sitekey': 'my-secret-key',
'theme': 'dark',
'callback': verifyCallback
})
isRendered = true;
}
})
}

Based on the code you have posted you have the following options:
1st option use then
contactForm.addEventListener('submit', (e) => {
e.preventDefault();
captchaVerification().then((response) => {
// check if response is ok
if(response ... is not what expected end here) {
return false;
}
const data = new FormData(contactForm)
_alert.innerHTML = 'Sending message...'
_alert.setAttribute('class', `alert show`);
ajax(contactForm.method, contactForm.action, data)
.then(([r, t]) => outcome(r ? r = 'success' : r = 'error', t))
.catch(e => outcome('error', e))
});
});
2nd option use await
contactForm.addEventListener('submit', async (e) => {
e.preventDefault();
let response = await captchaVerification();
// check if response is ok
if(response ... is not what expected end here) {
return false;
}
const data = new FormData(contactForm)
_alert.innerHTML = 'Sending message...'
_alert.setAttribute('class', `alert show`);
ajax(contactForm.method, contactForm.action, data)
.then(([r, t]) => outcome(r ? r = 'success' : r = 'error', t))
.catch(e => outcome('error', e))
});
You could also try changing two-three lines in your hastebin example.
var isRendered = false;
async function captchaVerification() {
return await new Promise((resolve, reject) => {
captcha.style.display = 'block'
const verifyCallback = (response) => {
if (response) {
captcha.style.display = 'none'
grecaptcha.reset()
return resolve(response)
} else {
grecaptcha.reset()
return reject('Invalid captcha verification.')
}
}
if (!isRendered) {
grecaptcha.render('g-recaptcha', {
'sitekey': 'my-secret-key',
'theme': 'dark',
'callback': verifyCallback
})
isRendered = true;
}
})
}

It purely depends on captchaVerification implementation. If function return the same promise. It won't resolve next time. It is like Singelton. so better create new promise always.
const promise = new Promise(r => setTimeout(r, 100, Math.random()))
const captchaVerification = () => promise;
const captchaVerificationRe = () => new Promise(r => setTimeout(r, 100, Math.random()));
async function main() {
let res = await captchaVerification();
console.log(res); // 0.3251446189302283
res = await captchaVerification("sffsfs");
console.log(res); // 0.3251446189302283
res = await captchaVerificationRe();
console.log(res); // 0.06299211055753262
res = await captchaVerification("sffsfs");
console.log(res); // 0.721527810718094
}
main();

For everyone wondering, this is the piece of code that solved my issue.
Basically Google API does not allow user to render same captcha object on same HTML Element more than once. I solved it by dynamically creating HTML Element for my captcha and removing it after I receive the response from the Google API server-side verification (success/error).
https://gist.github.com/ANTOSzbk/75ed7003e914162550f61399122a3bd4

Related

postMessage to iframe with a return MessageChannel using async await

I cant get async-await to work when using postMessage and a MessageChannel
const iframe = document.querySelector("iframe");
const sendMsgOnPort = async (msg) => {
const channel = new MessageChannel();
const testfunc = async () => {
channel.port1.onmessage = ({ data }) => {
channel.port1.close();
if (data.error) {
return data.error;
} else {
return data.result;
}
};
};
iframe.contentWindow.postMessage(`${msg}`, "*", [channel.port2]);
await testfunc();
};
iframe.addEventListener(
"load",
async () => {
console.log(await sendMsgOnPort("msg"));
},
true
);
and the child I have
window.addEventListener("message", (event) => {
try {
event.ports[0].postMessage({ result: `${event.data} back` });
} catch (e) {
event.ports[0].postMessage({ error: e });
}
});
I can get it to work with
const sendMsgOnPort = (msg) =>
new Promise((res, rej) => {
const channel = new MessageChannel();
channel.port1.onmessage = ({ data }) => {
channel.port1.close();
if (data.error) {
rej(data.error);
} else {
res(data.result);
}
};
iframe.contentWindow.postMessage(`${msg}`, "*", [channel.port2]);
});
but is there a way to do this without the new Promise((res, rej) => {
No.
You can only (usefully) await a promise.
You need new Promise to create one if you don't have one already (e.g. if the underlying API you are using returns a promise).
You don't have one already (not least because onmessage is a callback API designed to handle 𝑛 messages, not 1 message).

Can not return from a function

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.

After post request, continue checking for completion | Node.js

I'm making a post request with a good amount of data that will take about a minute to finish saving. The hosting service I'm using for this will time out requests after 5 seconds, so I need to set this up to periodically check if the data saving is complete to give a final update.
I'm using request-promise, and have looked at both setTimeout and setInterval approaches. In my latest attempt (below) I'm using a setTimeout approach, but my second then keeps being called pretty much immediately. I want this to hang out in the first then stage until it's checked a bunch of times (24 here) or actually finished.
I might have a totally wrong approach here, but I'm not finding examples of the thing I'm trying to reference. Any direction to a good example of this or where I'm going wrong would be greatly appreciated.
const request = require('request-promise');
function checkFiles () {
return request({
uri: `${process.env.ROOT_URL}/api/v1/get/file-processing`,
method: 'GET',
json: true
})
.then(res => { return res; })
.catch(err => { return err; });
}
async function init () {
const filesPostOptions = {/* request options */};
await request(filesPostOptions)
.then(async status => { // THEN #1
if (status.status === 201) {
return status;
}
let checks = 0;
const checkIt = function() {
checks++;
checkFiles()
.then(res => {
if (res.status === 201 || checks > 24) {
status = res;
return status;
} else {
setTimeout(checkIt, 5000);
}
})
.catch(err => {
console.error(err);
});
};
checkIt();
})
.then(status => { // THEN #2
if (!status.status) {
throw Error('Post request timed out.');
}
return status;
})
.catch(err => {
err = err.error ? err.error : err;
console.error(err);
});
}
The post response will deliver a response with a status property (the status code) and a message property.
You need to control the return in "THEN #" by adding a Promise:
.then(async status => { // THEN #1
return new Promise((resolve, reject) => { // <---- prevent an immediate return
if (status.status === 201) {
return resolve(status);
}
let checks = 0;
const checkIt = function() {
checks++;
checkFiles()
.then(res => {
if (res.status === 201 || checks > 24) {
status = res;
resolve(status);
} else {
setTimeout(checkIt, 1000);
}
})
.catch(err => reject(err));
};
checkIt();
})
})

Promise not executing

Promise not executing as expected. Not sure what I am doing wrong here, the then after promise all never executes.
exports.handler = (event, database, defaultBucket) => {
if( event.data.val() ){
const { userId, postId } = event.params;
createFolderInTmp(postId);
return event.data.ref.parent.once("value", ((snap) => {
//getpostData data
const postData = snap.val();
var downloadPromises = [];
const images = [];
postData.slides.forEach(slide => {
images.push(slide.value);
});
return Promise.all([
...images.map((image) => {
return downloadImageAsync(image, postId, defaultBucket);
})
])
.then(() => {
//-----NEVER EXECUTES THIS STEP, SIMPLY GOES BLANK HERE----
createAGIF(postData, postId);
return console.log('this completed');
})
.then(() => {
return uploadImageAsync( getLocalGIFFilePath(postId), getDestinationUrl(userId, postId) ,'image/gif',defaultBucket)
})
.then(() => {
return updateShareableUrl(userId, postId, defaultBucket);
});
}));
}else{
console.log('No Data available');
return false;
}
};
Adding related functions
function downloadImageAsync(imageUrl, storyId, bucket){
const tempFilePath = getTempDownloadUrl(storyId, encodeURIComponent(imageUrl));
return bucket.file(imageUrl).download({ destination: tempFilePath});
}
Can someone point out what I am doing wrong here?
I was going about this in the wrong way. The moment event.data.ref.parent.once resolves, the job of this function is done, and whatever I return from inside the callback, does not matter anymore. The function execution stops. The resources are deallocated. It was not until Bergi pointed out of handling errors, but in this case there were no errors.

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