resolve() executes but promise not resolves as expected - javascript

I have such code: (I put here the whole code in order not to miss anything)
let updateCoverLoaded;
function coverLoaded(cover){
clearInterval(updateCoverLoaded);
updateCoverLoaded = setInterval(() => coverLoaded(cover), 25);
const coverSelector = $(`[aria-label="${cover}"]`).find("image")[0];
return new Promise(resolve => {
if(typeof coverSelector !== "undefined"){
const coverSelectorSource = coverSelector.getAttribute("xlink:href");
if(getCoverWidth(coverSelectorSource) !== 0) {
clearInterval(updateCoverLoaded);
setTimeout(() => {
player.SetVar(`${cover}`, true);
setTimeout(() => player.SetVar(`${cover}`, false), 100);
resolve();
}, 100);
}
}
});
function getCoverWidth(src) {
const image = new Image();
image.src = src;
return image.naturalWidth;
}
}
// Add SFX to cover entrance and exit
async function feedbackCover(){
await coverLoaded('coverB');
console.log('after resoving promise as expected!');
}
Although code reaches the player.SetVar(${cover}, true); line of code and I'm sure that resolve() is executed the promise doesn't resolve and I can't see console.log('after resoving promise as expected!'); Why? it works in some other situations!
Note: The code actually waits for an element (an image) to load and then resolves the promise.

There are two possible problems, the first is that if the image isn't loaded yet it the first Promise doesn't resolve and all subsequent Promises are discarded, the second is that if coverLoaded is called again, the first promise will never resolve.
This should work as expected:
function coverLoaded(cover) {
return new Promise((resolve) => {
const updateCoverLoaded = setInterval(() => {
const coverSelector = $(`[aria-label="${cover}"]`).find("image")[0];
if (typeof coverSelector !== "undefined") {
const coverSelectorSource = coverSelector.getAttribute("xlink:href");
if (getCoverWidth(coverSelectorSource) !== 0) {
clearInterval(updateCoverLoaded);
setTimeout(() => {
player.SetVar(`${cover}`, true);
setTimeout(() => player.SetVar(`${cover}`, false), 100);
resolve();
}, 100);
}
}
}, 25);
});
function getCoverWidth(src) {
const image = new Image();
image.src = src;
return image.naturalWidth;
}
}
// Add SFX to cover entrance and exit
async function feedbackCover() {
await coverLoaded("coverB");
console.log("after resoving promise as expected!");
}

Related

Trouble promisifying setInterval to do an action continuously for a given period and then allow a new asynchronous function to begin

Having trouble promisifying setInterval. The .then() is occurring immediately despite the embedEditor function returning a promise only if time === 0. How do I make a function in setInterval return a promise once a time period has completed?
const interval = setInterval(embedEditor, 1000);
async function embedEditor() {
time -= 1;
//inner function....//
if (time === 0) {
clearInterval(interval);
return new Promise((res) => { res("time period over"); });
}
}
await interaction.reply({ embeds: [exampleEmbed], components: [row, row2] });
await interval
.then(/*action*/);
Answer from Fannie Zemlak that works like a charm:
const asyncInterval = async (callback, ms, triesLeft = 5) => {
return new Promise((resolve, reject) => {
const interval = setInterval(async () => {
if (await callback()) {
resolve();
clearInterval(interval);
} else if (triesLeft <= 1) {
reject();
clearInterval(interval);
}
triesLeft--;
}, ms);
});
}

Popups- one after the other

I want to show to user popups for 2 seconds, one after one (could be any number suppose 17).
any ideas how to do that?
function sleep(miliseconds) {
var currentTime = new Date().getTime();
while (currentTime + miliseconds >= new Date().getTime()) {
}
}
/*First option*/
function printFailureMessages (failureMessages){
failureMessages.forEach(message => {
document.getElementById("myPopup").innerHTML = message;
document.getElementById("myPopup").classList.toggle("show");
sleep(1500);
document.getElementById("myPopup").classList.toggle("show");
});
}
/*Second option*/
function printFailureMessages (failureMessages){
failureMessages.forEach(message => {
document.getElementById("myPopup").innerHTML = message;
document.getElementById("myPopup").classList.toggle("show");
setTimeout(function(){
document.getElementById("myPopup").classList.toggle("show");
},1500);
});
}
works well when failureMessages size is one...
BTW - i tried some more variations with setTimeOut , not successful..
You can try using async/await for this like:
// Return a promise that resolves after "miliseconds" milliseconds
function sleep(miliseconds) {
return new Promise(res => setTimeout(res, miliseconds));
}
// Make this function async function
async function printFailureMessages(failureMessages) {
for (var i = 0; i < failureMessages.length; i++) {
document.getElementById("myPopup").innerHTML = failureMessages[i];
document.getElementById("myPopup").classList.toggle("show");
await sleep(1500); // wait for this promise to finish
document.getElementById("myPopup").classList.toggle("show");
}
}
printFailureMessages(['Hello', 'World']);

Resolve the promise only if xhr.onloadend has fired before

I want to assign videoLoaded to true right after myVideo.mp4 is fully loaded. I can do this at the last lines of the code (This is our promise):
preload.fetch([
clipSource
]).then(items => {
// Using a promise it'll fire when we are sure that video clip has finished loading completely
videoLoaded = true;
});
The first issue is if our URL is not valid we get a 404 response status code. the 404 itself is a valid response so we will not trigger xhr.onerror() because technically it's not an error.
we can track 404 status using:
xhr.onloadend = function() {
if(xhr.status == 404) { // do something }
}
The issue is onloadend event fired only after the promise .then(items => { .... so if there is not a valid URL we can not prevent the promise to resolve and videoLoaded will be assigned to true although there is not a valid URL...
I want to resolve the promise and assign videoLoaded to true only if xhr.status !== 404 in this situation we can be sure that we have a valid URL.
Here is the code (I have used a setInterval and it works but I think there are cleaner solutions that you can share):
let onLoadPassed = false;
let videoLoaded = false;
let clipSource = 'https://mysite/myVideo.mp4';
preload();
// Make sure the video clip is fully loaded
function preload(){
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.Preload = factory());
}(this, (function () { 'use strict';
function preloadOne(url, done) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onprogress = event => {
if (!event.lengthComputable) return false
let item = this.getItemByUrl(event.target.responseURL);
item.completion = parseInt((event.loaded / event.total) * 100);
item.downloaded = event.loaded;
item.total = event.total;
this.updateProgressBar(item);
};
xhr.onload = event => {
let type = event.target.response.type;
let blob = new Blob([event.target.response], { type: type });
let url = URL.createObjectURL(blob);
let responseURL = event.target.responseURL;
let item = this.getItemByUrl(responseURL);
item.blobUrl = url;
item.fileName = responseURL.substring(responseURL.lastIndexOf('/') + 1);
item.type = type;
item.size = blob.size;
done(item);
};
xhr.onerror = event => {
console.log('Error has happend so we restart the video preloading..');
preload();
};
xhr.onloadend = function() {
if(xhr.status == 404){
console.log('404 not found');
onLoadPassed = false;
} else {
console.log('File exist');
onLoadPassed = true;
}
}
xhr.send();
}
function updateProgressBar(item) {
var sumCompletion = 0;
var maxCompletion = this.status.length * 100;
for (var itemStatus of this.status) {
if (itemStatus.completion) {
sumCompletion += itemStatus.completion;
}
}
var totalCompletion = parseInt((sumCompletion / maxCompletion) * 100);
if (!isNaN(totalCompletion)) {
this.onprogress({
progress: totalCompletion,
item: item
});
}
}
function getItemByUrl(rawUrl) {
for (var item of this.status) {
if (item.url == rawUrl) return item
}
}
function fetch(list) {
return new Promise((resolve, reject) => {
this.loaded = list.length;
for (let item of list) {
this.status.push({ url: item });
this.preloadOne(item, item => {
this.onfetched(item);
this.loaded--;
if (this.loaded == 0) {
this.oncomplete(this.status);
resolve(this.status);
}
});
}
})
}
function Preload() {
return {
status: [],
loaded: false,
onprogress: () => {},
oncomplete: () => {},
onfetched: () => {},
fetch,
updateProgressBar,
preloadOne,
getItemByUrl
}
}
return Preload;
})));
const preload = Preload();
preload.fetch([
clipSource
]).then(items => {
// Fired when we are sure that video clip has finished loading completely
let check = setInterval(passedFunc, 50);
function passedFunc() {
if(onLoadPassed === true){
videoLoaded = true;
clearInterval(check);
console.log('videoLoaded: ' + videoLoaded);
};
}
});
};
You can intercept the promise and throw an error if the status code is 404, this way the subsequent .then statements will be ignored and the result will be captured by the .catch statement.
preload.fetch([
clipSource
])
.then(response => {
if(!response.ok) //better to use response.ok as it checks a range of status codes
throw Error(response.statusText);
return response;
})
.then(items => {
// Using a promise it'll fire when we are sure that video clip has finished loading completely
videoLoaded = true;
})
.catch(error => {
//do something
console.log(error)
});

Promise returning unexpectedly

I have the following:
return indexedDbClient.getStorageUsedInGb().then(function (storageUsedInGb) {
var evictedMediaGuids = [];
storageUsedInGb = parseFloat(storageUsedInGb);
if (storageUsedInGb > storageQuotaInGb) {
return new Promise(function(resolve, reject){
const store = database.transaction(storeName, "readwrite").objectStore(storeName);
(function loop(storageUsedInGb) {
if (storageUsedInGb <= storageQuotaInGb) {
resolve({
evictedMediaGuids: evictedMediaGuids,
shouldStopStoring: false
});
} else {
const latestMediaRequest = store.getAll();
latestMediaRequest.onsuccess = function (event) {
const allData = event.target.result;
const targetEntry = allData[0];
const deleteRequest = store.delete(targetEntry.media.guid);
evictedMediaGuids.push(targetEntry.media.guid);
deleteRequest.onsuccess = loop.bind(null, storageUsedInGb - event.target.media.size / 1024 / 1000 / 1000);
deleteRequest.onerror = reject;
}
latestMediaRequest.onerror = reject;
}
})(storageUsedInGb); // call immediately
})
} else {
return Promise.resolve({
evictedMediaGuids: evictedMediaGuids,
shouldStopStoring: false
});
}
}).then(function (storeObject) {
// do stuff to object
return Promise.resolve(storeObject)
});
The idea is that loop(storageUsedInGb) forces the resolution to wait for the return; however handleStoreObject gets invoked immediately after loop - with no sign of the latestMediaRequest onsuccess handler being invoked. What am I doing wrong?
I am using bluebird in case it matters.

Wait until a condition is true?

I'm using navigator.geolocation.watchPosition in JavaScript, and I want a way to deal with the possibility that the user might submit a form relying on location before watchPosition has found its location.
Ideally the user would see a 'Waiting for location' message periodically until the location was obtained, then the form would submit.
However, I'm not sure how to implement this in JavaScript given its lack of a wait function.
Current code:
var current_latlng = null;
function gpsSuccess(pos){
//console.log('gpsSuccess');
if (pos.coords) {
lat = pos.coords.latitude;
lng = pos.coords.longitude;
}
else {
lat = pos.latitude;
lng = pos.longitude;
}
current_latlng = new google.maps.LatLng(lat, lng);
}
watchId = navigator.geolocation.watchPosition(gpsSuccess,
gpsFail, {timeout:5000, maximumAge: 300000});
$('#route-form').submit(function(event) {
// User submits form, we need their location...
while(current_location==null) {
toastMessage('Waiting for your location...');
wait(500); // What should I use instead?
}
// Continue with location found...
});
Modern solution using Promise
function waitFor(conditionFunction) {
const poll = resolve => {
if(conditionFunction()) resolve();
else setTimeout(_ => poll(resolve), 400);
}
return new Promise(poll);
}
Usage
waitFor(_ => flag === true)
.then(_ => console.log('the wait is over!'));
or
async function demo() {
await waitFor(_ => flag === true);
console.log('the wait is over!');
}
References
Promises
Arrow Functions
Async/Await
Personally, I use a waitfor() function which encapsulates a setTimeout():
//**********************************************************************
// function waitfor - Wait until a condition is met
//
// Needed parameters:
// test: function that returns a value
// expectedValue: the value of the test function we are waiting for
// msec: delay between the calls to test
// callback: function to execute when the condition is met
// Parameters for debugging:
// count: used to count the loops
// source: a string to specify an ID, a message, etc
//**********************************************************************
function waitfor(test, expectedValue, msec, count, source, callback) {
// Check if condition met. If not, re-check later (msec).
while (test() !== expectedValue) {
count++;
setTimeout(function() {
waitfor(test, expectedValue, msec, count, source, callback);
}, msec);
return;
}
// Condition finally met. callback() can be executed.
console.log(source + ': ' + test() + ', expected: ' + expectedValue + ', ' + count + ' loops.');
callback();
}
I use my waitfor() function in the following way:
var _TIMEOUT = 50; // waitfor test rate [msec]
var bBusy = true; // Busy flag (will be changed somewhere else in the code)
...
// Test a flag
function _isBusy() {
return bBusy;
}
...
// Wait until idle (busy must be false)
waitfor(_isBusy, false, _TIMEOUT, 0, 'play->busy false', function() {
alert('The show can resume !');
});
This is precisely what promises were invented and implemented (since OP asked his question) for.
See all of the various implementations, eg promisejs.org
You'll want to use setTimeout:
function checkAndSubmit(form) {
var location = getLocation();
if (!location) {
setTimeout(checkAndSubmit, 500, form); // setTimeout(func, timeMS, params...)
} else {
// Set location on form here if it isn't in getLocation()
form.submit();
}
}
... where getLocation looks up your location.
You could use a timeout to try to re-submit the form:
$('#route-form').submit(function(event) {
// User submits form, we need their location...
if(current_location==null) {
toastMessage('Waiting for your location...');
setTimeout(function(){ $('#route-form').submit(); }, 500); // Try to submit form after timeout
return false;
} else {
// Continue with location found...
}
});
export default (condition: Function, interval = 1000) =>
new Promise((resolve) => {
const runner = () => {
const timeout = setTimeout(runner, interval);
if (condition()) {
clearTimeout(timeout);
resolve(undefined);
return;
}
};
runner();
});
class App extends React.Component {
componentDidMount() {
this.processToken();
}
processToken = () => {
try {
const params = querySearch(this.props.location.search);
if('accessToken' in params){
this.setOrderContext(params);
this.props.history.push(`/myinfo`);
}
} catch(ex) {
console.log(ex);
}
}
setOrderContext (params){
//this action calls a reducer and put the token in session storage
this.props.userActions.processUserToken({data: {accessToken:params.accessToken}});
}
render() {
return (
<Switch>
//myinfo component needs accessToken to retrieve my info
<Route path="/myInfo" component={InofUI.App} />
</Switch>
);
}
And then inside InofUI.App
componentDidMount() {
this.retrieveMyInfo();
}
retrieveMyInfo = async () => {
await this.untilTokenIsSet();
const { location, history } = this.props;
this.props.processUser(location, history);
}
untilTokenIsSet= () => {
const poll = (resolve) => {
const { user } = this.props;
const { accessToken } = user;
console.log('getting accessToken', accessToken);
if (accessToken) {
resolve();
} else {
console.log('wating for token .. ');
setTimeout(() => poll(resolve), 100);
}
};
return new Promise(poll);
}
Try using setInterval and clearInterval like this...
var current_latlng = null;
function gpsSuccess(pos) {
//console.log('gpsSuccess');
if (pos.coords) {
lat = pos.coords.latitude;
lng = pos.coords.longitude;
} else {
lat = pos.latitude;
lng = pos.longitude;
}
current_latlng = new google.maps.LatLng(lat, lng);
}
watchId = navigator.geolocation.watchPosition(gpsSuccess,
gpsFail, {
timeout: 5000,
maximumAge: 300000
});
$('#route-form').submit(function (event) {
// User submits form, we need their location...
// Checks status every half-second
var watch = setInterval(task, 500)
function task() {
if (current_latlng != null) {
clearInterval(watch)
watch = false
return callback()
} else {
toastMessage('Waiting for your location...');
}
}
function callback() {
// Continue on with location found...
}
});
This accepts any function, even if it's async, and when it evaluates to a truthy value (checking every quarter-second by default), resolves to it.
function waitFor(condition, step = 250, timeout = Infinity) {
return new Promise((resolve, reject) => {
const now = Date.now();
let running = false;
const interval = setInterval(async () => {
if (running) return;
running = true;
const result = await condition();
if (result) {
clearInterval(interval);
resolve(result);
} else if (Date.now() - now >= timeout * 1000) {
clearInterval(interval);
reject(result);
}
running = false;
}, step);
});
}
Example
example();
async function example() {
let foo = 'bar';
setTimeout(() => foo = null, 2000);
console.log(`foo === "${foo}"`);
await waitFor(() => foo === null);
console.log('2 seconds have elapsed.');
console.log(`foo === ${foo}`);
}
function waitFor(condition, step = 250, timeout = Infinity) {
return new Promise((resolve, reject) => {
const now = Date.now();
let running = false;
const interval = setInterval(async () => {
if (running) return;
running = true;
const result = await condition();
if (result) {
clearInterval(interval);
resolve(result);
} else if (Date.now() - now >= timeout * 1000) {
clearInterval(interval);
reject(result);
}
running = false;
}, step);
});
}

Categories