I have a javascript file where I'm grabbing a user's geolocation. I've wrapped it in a promise to make sure that it returns back coordinates.
geoLocation.js:
const getGeoLocation = () => {
return new Promise(function(resolve, reject){
function positionSuccess(position){
resolve(position);
}
function positionError(error){
reject('ERROR: ' + error.code + ': ' + error.message);
};
navigator.geolocation.getCurrentPosition(positionSuccess, positionError);
});
};
async function userCoords() {
const response = await getGeoLocation();
}
module.exports = userCoords;
From here I'm importing the async userCoords function into a Main.js react component. In it's render method I'm asking it to console.log(userCoords()), but what I'm getting in the console is:
It looks like it's still waiting to finish with my getGeoLocation promise. What should I be doing to make sure that getGeoLocation is finished completely and that I can pass on it's return coordinates to my Main.js component?
Thank you!
An async function will always return a promise, even if it is already settled. You must use promise.then or await. If you can make your render function async, then you can write:
console.log(await userCoords());
A function being async is poisonous, meaning that any function calling an async function (and expecting a settled value) must be itself async. This is particularry painful when programming user interface, because UI codes are inherently synchronuous. Showing data which comes from an asynchronouos datasource is hard to do well and demand careful engineering.
Maybe the simplest thing you can do is to poll the user coordinates independently from your render cycle:
var coords_promise = null;
var coords = null;
function render() {
if (!coords_promise) {
coords_promise = userCoords().then(function(newCoords) {
coords = newCoords;
coords_promise = null;
});
}
console.log(coords);
}
setInterval(render, 20);
This piece of code will start fetching the coordinates at invocation to the render function. This means the first few cycles of the render function will print the initial value of the coords variable, but after then it will use the newest settled return value of userCoords(). This code also makes sure that no fetching is made if no call was made to the render() function, and no parallel invocations will be made to userCoords().
Related
I'm making a request to a service that generates several strings in rapid succession. My problem arise from having to return a promise with this service, as I do not control the service process that returns the string.
I want to emphasize the fact that I necessarily need to return a promise.
Right now, what I have is the main function (handler.ts) and it doesn't mantain any business logic, including only the following:
public static callback: any;
public static async init(configuration, callback): Promise<any> {
this.callback = callback;
...
return new Promise((resolve, reject) => {
try {
const result = await Service.bootstrap(configuration)
return resolve(result)
} catch(err) {
reject(err)
}
}
}
This handler calls the service, which has the bootstrap function that performs a call to a .js file that obtains some information from my computer and then returns a string about it.
import loadComputerInformation from 'load.js'
public async bootstrap() : Promise<any> {
loadComputerInformation();
function useComputerInfoString() {
// window.getInfo is generated by loadComputerInformation(), and I can not control
// when does it return it, as it generates several strings with rapid succession
// and until the function exists, I will not get my string.
if (typeof window.getInfo !== 'function') {return;}
const data = window.getInfo();
if (data.finished) {
clearTimeout(timeoutId);
const infoString = data.computerInfo;
Service.axiosCall(infoString);
}
}
// I have to set an interval to perform the call several times, and until it resolves it
// it will not stop performing this call.
const timeoutId = setInterval(useComputerInfoString, 500);
}
return;
}
Therefore, the problem that I'm facing is that my promise gets lost in another thread, and I can not return the value from Service.axiosCall(infoString), which is just a standard axios call, but that necessarily needs the infoString.
Adding the axios call function just in case it is useful. Right now, I'm using the callback passed to the handler.js to return the axios call, but I want to be able to include it in the Promise without the necessity of a callback function
public static async axiosCall(blackbox): Promise<any> {
await axios.post('https://somecall.com/info', blackbox)
.then((response) => { this.callback(element)
return element);
}
}
Any idea of how to solve this?
Highlight
Please note that loadComputerInformation() asynchronously loads Window.getInfo(), but it does not resolve only one value, but several, therefore I can not await on Window.getInfo() because at the beggining it does not exist, and will only return undefined
Also, right now, the code is up and running, but the way it is returning the value is with the callback and not as a promise.
Try a bootstrap function whose returned promise resolves with the response returned from an axios call.
This suggestion (based on information in the post) is vanilla JavaScript to demonstrate how to the problem might be approached. Obviously modification to integrate it into the application will be needed:
const bootstrap = () => new Promise( resolve => {
loadComputerInformation();
let state = "load";
const timer = setInterval( ()=> {
if( state == "load") {
if( typeof window.getData != "function") {
return;
}
state = "get";
}
let data;
if( state == "get") {
data = window.getData();
if(!data.finished) {
return;
}
// state = "request";
}
clearInterval(timer);
resolve( axios('post', "https:example.com/info", data.computerInfo) );
}, 100); // 1/10th second?
};
Note this answer has been modified to use a state machine to wait for getData to be loaded asynchronously, then to wait for a call to getData to return a data object with a finished property, before resolving the promise returned with the promise returned by the axios call - the first answer was simpler right up until it needed to wait for getData code to be loaded asynchronously.
Beneath the question lies a problem that may have to be written off to code debt: JavaScript is event driven. loadComputerInformation appears to have been written for its results to be polled. It is hard to imagine a justifiable reason why it was left in that state.
my code looks like below. it throws the error where i am returning null in both the cases. Should i return null in the end of the function as well? i was afraid if that will make function not to wait finishing
exports.on_user_created_update_generate_barcode = functions.database.ref("/users/{id}")
.onCreate((snapshot, context) => {
console.log("start of on_user_created_update_generate_barcode ")
const user = snapshot.val();
const referralCode = user._referralCode
const uid = context.params.id
console.log("Referral code is:" + referralCode + " for user:" + uid)
bwipjs.toBuffer({
bcid: 'code128', // Barcode type
text: referralCode, // Text to encode
scale: 3, // 3x scaling factor
height: 10, // Bar height, in millimeters
includetext: true, // Show human-readable text
textxalign: 'center', // Always good to set this
}, function (err, png) {
if (err) {
console.err("failed to generate bar code:::" + err)
return null
} else {
console.log("png generated")
//console.log(png)
const pngImg = 'data:image/png;base64,' + png.toString('base64')
var db = admin.database();
var userRef = db.ref('users')
return userRef.child(uid).update({"_barcode": pngImg})
}
});
})
The error message is correct - you are are not returning anything from your function, which is the same as returning undefined. What you are doing instead is return null from a function callback that you passed to bwipjs.toBuffer(). The return value of that inner function does not propagate to the outer function.
If you just return null from the top level of your function, you may stop that error messages, but the code will not work correctly. Cloud Functions will shut down all asynchronous work that was started inside the function after the function returns a value that's not a promise. It will likely appears to do nothing at all.
What you will need to do is return a promise that resolves when all the asynchronous work is complete in your function. Right now, that looks like the call to toBuffer(), and also the update() to Realtime Database.
You might find the documentation on async programming in functions helpful. You may also want to watch this video series on dealing with promises in Cloud Functions.
I am trying to get my head around with async/await
I am using https://github.com/sunnylqm/react-native-storage for my project in react-native.
I am using async storage to store few critical information such as selected user locale, I need to retrieve this value before screen is rendered to display screen based on selected locale.
I have tried implementing several helper functions, it works with callback, what I need is to return the value instead of callback and wait until the value is fetched. Below are few examples I tried.
// 1
_selectedLocale = async () => {
try {
const value = await global.storage.load({key: 'selectedLocale'});
return value
} catch (error) {
console.log(value)
}
}
var selectedLocale = _selectedLocale();
// 2
export async function _selectedLocale() {
return storage.load({key: 'selectedLocale'});
}
var selectedLocale = _selectedLocale();
// 3
export function selectedLocale(callback) {
storage.load({key: 'selectedLocale'}).catch(e => {
callback(RNLanguages.language);
}).then(function(locale) {
callback(locale);
});
}
This is not working, I am looking to
Implement a helper function to retrieve a value based on key
Wait until the value is retrieved (sync)
Can someone point me to right direction
Thanks
UPDATE1:
Here is how I am using the callback
selectedLocale(function(locale) {
global.layoutIsRTL = 'ar' == locale;
ReactNative.I18nManager.allowRTL(true);
global.i18n.locale = locale
});
UPDATE2:
It seems someone has already done this, here is the reference https://github.com/sunnylqm/react-native-storage/issues/206 unfortunately I am having hard time understanding it.
async implicitly returns a promise, so you need to await the function call as well if you want the value there:
var selectedLocale = await _selectedLocale();
but you can only do that inside of another async function
I have a class that encapsulate communication with a server
and it has a getOrCreate method to create a ShoppingCart resource in the server (I am using firebase, but my question is for all kind of server implementations)
Once the application is loaded in the client (I am using angular but again it does not matter for this question), two different components (areas of the screen) get the service class injected to them (it is a singleton) and they call a method getOrCreateCart()
This method should check in the localStorage if a cartId exists and if so return it. Otherwise, it should create it with an async call to the server.
The issue is that if both components call this method at the same time, with the lack of locking mechanisme, I can not block the second operation until the first one is completed and on the second time return the cartId from the localStrogate instead of creating another resource in the database
Checking if cartId already exists in the database is not an option, since the cartId is generated upon request to the server.
A little bit of code to make things more clear:
private async getOrCreateCartId() {
let cartId = localStorage.getItem('cartId');
if (cartId) return cartId;
let newCartId = await this.create();
localStorage.setItem('cartId', newCartId);
return newCartId;
}
// Returns a promise with the new created id
private create() {
return this.db.list('/shopping-carts/').push({
dateCreated: new Date().getTime()
});
}
Thanks to Roamer-1888 I've figured it out
This is my solution:
Cache the promise that returns from the create() method
For all subsequent calls, return this promise so when the result will arrive from the server, all the returned promises will be invoked
Before returning the promise, I call then and transform the value from the server to a simple string that I am interested in.
I ended up with this code:
private getOrCreateCartId() {
let cartId = localStorage.getItem('cartId');
if (cartId) return Promise.resolve(cartId);
if (this.createPromise) return this.createPromise.then(result => result.key);
this.createPromise = this.create();
this.createPromise.then(result => {
localStorage.setItem('cartId', result.key);
})
return this.createPromise.then(result => result.key);
}
Congratulations on figuring it out.
Here's what I ended up with, starting with an emulation of your singleton :
// singleton FOO
const FOO = (function() {
var cartId = localStorage.getItem('cartId'); // cache, initialised from localStorage
async function getOrCreateCartId() {
if (!cartId) {
cartId = create(); // cache promise
localStorage.setItem('cartId', await cartId);
}
return cartId;
}
// Returns a promise with the new created id
function create() {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(Date.now());
}, 2000);
});
}
return {
'getOrCreateCartId': getOrCreateCartId
};
}());
At first glance, you would think getOrCreateCartId() is synchronous but that's an affect of async/await, which allow async code to be structured almost identically to sync code.
And here are two rapid-fire calls to FOO.getOrCreateCartId()
// call from component 1
FOO.getOrCreateCartId().then(function(cartId) {
console.log(cartId);
});
// call from component 2
FOO.getOrCreateCartId().then(function(cartId) {
console.log(cartId);
});
The console will log the same cartId twice even if the call from component 1 needs to call create().
DEMO: I included a .clear() method so you can play around with it and convice yourself it works as it should.
I need helps on notify() within the promise chain.
I have 3 promise base functions connect(), send(cmd), disconnect(). Now I would like to write another function to wrap those call in following manner with progress notification.
function bombard() {
return connect()
.then(function () {
var cmds = [/*many commands in string*/];
var promises = _.map(cmds, function (cmd) {
var deferred = Q.defer();
deferred.notify(cmd);
send(cmd).then(function (result) {
deferred.resovle(result);
});
return deferred.promise;
});
return Q.all(promises);
})
.finally(function () { return disconnect() })
}
Run the function like that
bombard.then(onResolve, onReject, function (obj) {
console.log(ob);
});
I supposed I will get notification for every command I have sent. However, it does not work as I expected. I get nothing actually.
Although I believe this is due to those notifications havn't propagated to outside promise, I have no idea on how to propagated those notifications on Q or wrapping that promise chain: connect, send, disconnect in a one deferred object.
Thanks
I have some good news and some bad news.
Very good! You have found out the problem with the notifications API and why it is being removed in Q in the v2 branch, being deprecated in newer libraries like Bluebird, and never included in ECMAScript 6. It really boils down to the fact promises are not event emitters.
The notifications API does not compose or aggregate very well. In fact, notifications being on promises does not make too much sense imo to begin with,.
Instead, I suggest using a progress notification even, kind of like IProgress in C#. I'm going to simulate all the actions with Q.delay() for isolation, your code will obviously make real calls
function connect(iProgress){
return Q.delay(1000).then(function(res){
iProgress(0.5,"Connecting to Database");
}).delay(1000).then(function(res){
iProgress(0.5,"Done Connecting");
});
}
function send(data,iProgress){
return Q.delay(200*Math.random() + 200).then(function(res){
iProgress(0.33, "Sent First Element");
}).delay(200*Math.random() + 400).then(function(){
iProgress(0.33, "Sent second Element");
}).delay(200*Math.random() + 500).then(function(){
iProgress(0.33, "Done sending!");
});
}
// disconnect is similar
Now, we can easily decide how our promises compose, for example:
function aggregateProgress(num){
var total = 0;
return function(progression,message){
total += progression;
console.log("Progressed ", ((total/num)*100).toFixed(2)+"%" );
console.log("Got message",message);
}
}
Which would let you do:
// bombard can accept iProgress itself if it needs to propagate it
function bombard() {
var notify = aggregateProgress(cmds.length+1);
return connect(notify)
.then(function () {
var cmds = [/*many commands in string*/];
return Q.all(cmds.map(function(command){ return send(command,notify); }));
});
}
Here is a complete and working fiddle to play with