How to get the LocalStorage value out of promise scope


#1

Hi,

I know that If I want to retrieve or get the value from LocalStorage, need to follow below approach.

1| this.local.get("name").then((data) => { 2| alert(output); // prints the data 3| })

suppose I have the situation
1| this.local.get("name").then((data) => { 2| var output = data; 3| }) 4| console.log(output);

I get ‘undefined or error’ at line 4.

I want to do some manipulation with data (which is received inside LocalStorage .get() promise) outside of promise. How I can achieve this??

Thanks in advance.


Provider/server: Provide the data or contain the data?
Import of @types/q not working in my code
#2

The thing to keep in mind with promises is that they represent asynchronous tasks. So you can manipulate the data outside of the scope promise, but it has to be after the promise has finished.

So in your exact example, you can’t print the value outside of the scope, for a multitude of reasons. First being the fact that output isn’t in the scope that you’re trying to print it. Secondly, there’s no guarantee that the promise has finished executing before you’re trying to print the data it returns.

One way you can get around this, is save a reference to the promise and operate off of that, i.e.

var dataPromise = this.local.get("name");

...elsewhere...
dataPromise.then(data => {
   console.log(data);
});

Does this all make sense? I’m not sure what your experience with Javascript is, and so I hope I haven’t overloaded you. I know it took me a bit to wrap my head around promises the first time I ran into them :slight_smile:


#3

@SigmundFroyd Thanks for making clear and replying. But I often mess up with promise.

But suppose I want to get the value of ‘dataPromise’ in a console it also gives [Object object].

var dataPromise = this.local.get("name"); dataPromise.then(data => { console.log(data); }); console.log(dataPromise);

when I tried console.log(JSON.parse(dataPromise)); it gives me error as > Argument of type 'Promise<string>' is not assignable to parameter of type 'string'

and for console.log(JSON.stringify(dataPromise)); the output is > {"__zone_symbol__state":true,"__zone_symbol__value":"1468337734600"}


#4

In this case “dataPromise” represents the promise object, so it doesn’t really have a value. (I mean it does, but it doesn’t have anything interesting :stuck_out_tongue:)

dataPromise.then(data => {
console.log(data);
});

The “data” variable in this block will contain your actual data, and so I’m not sure that you have a reason to print the “dataPromise” to console. However, I may not be understanding what you’re trying to accomplish.


#5

@SigmundFroyd I am storing start_time value in LocalStorage on a Start button click.

On button click of Stop, I want to retrieve the start_time. And after that to calculate elapsed time I want to subtract this start_time from stop_time.

somehow like this…
var start_time = this.local.get('start_time'); // here you may retrieve using promise
var elapsed_time = stop_time - start_time;

so I want to know in which way I can retrieve the localStorage value so that I may able to subtract from stop_time
or the same value I can use further in my coding to manipulate it.


#6

one workaround is that,

put all the manipulation or operations inside ".then() function"… but that would be very messy and I think it would be not good practice too.

Anyone know another workaround for this problem. Or this might be an issue of ionic 2 LocalStorage .get() promises while dealing with the above-mentioned scenario.


#7

That, either directly or indirectly, is the only way to handle things.


#8

@rapropos but in case I have to do lengthy operation… then this should not be the good practice… right??

And suppose if I am retrieving two or more values from localstorage and using those two or more values for data manipulation inside single function i.e. getData()… then how it could be possible to work with them…

If i have start_time and stop_time inside localstorage… i want to use it in the getData() function then how I can retrieve both those localstorage value…to do operation like this…

var start_time = this.local.get('start_time').then(.......);
var stop_time = this.local.get('stop_time').then(.....)
var elapsed_time = stop_time - start_time;

I think this may not be applicable with above suggested approach…
And if possible can you plz help me out of this :pensive:


Storage get value
#9

There is no alternative. Until the promise resolves, the operation is not finished and the data is not available. If you’re concerned about user feedback, use a loading spinner or something, but then (or some equivalent alternative like converting to an observable and subscribing) is the only place where you can rely on the value.

Promise.all() or Observable.forkJoin().


#10

Thank u @rapropos :slight_smile:

Can u provide me any example of promise.all() or of Observable.forkjoin():sweat_smile:


#11

You’d do something like this:

var startPromise = this.local.get('start_time');
var stopPromise = this.local.get('stop_time');

Promise.all([startPromise, stopPromise]).then((start_time, stop_time) => {
var elapsed_time = stop_time - start_time;
...
});

(I apologize for any typos, it’s a tad difficult to type code on my phone!)


#12

Hello.
Your problem here is a mix of synchronous and asynchronous code.

There is no grantee that retrieving from locsl storafe finish before the next statement. In fact it doesn’t. Read this as “when is ready please call this callback which has something to do”

At first promise call you just register a callback to be called next time when the promise is evaluated.

Then the next statement is evaluated.

After your code end them the promise is evaluated and retrieve the local storage value.

Set a breakpoint in each statement in order to see execution flow.


#13

Should be:
Promise.all([startPromise, stopPromise]).then((arrayOfResults) => { ...


#14

Thank you. This solution solved my issue. I think I understand Promises a little better now.


#15

@rapropos, my problem is that am trying to get the data in localstorage and sent to mongodb, but the property is empty or something it does not just get to the db. have tried consoling but the log is {"__zone_symbol__state":true,"__zone_symbol__value":“data”}. when I tried the promise part, no luck. please suggest way i can get the data to the db. thanks


#16

@rapropos, i solved the issue, have to wrap the the promise call with the http call to nodejs, and that worked
let data= this.storage.get(‘data-from-localstorage’)
data.then(request =>{
let _data= data

this.service.add( _data)

})


#17

I have the following Load() method working but I would like to write something less repetitive and compact when loading several user options…

//
import { Injectable } from '@angular/core' // 20170515
import { Storage } from '@ionic/storage' // 20170515

//
@Injectable()
// 20170515
export class UserOptionsService {
    //
    constructor(
            public Storage: Storage
    ) {
        console.log("UserOptionsService------------>>Created");

        // TODO: how to handle this correctly since services are not expected to deal directly with views?
        // if page is destroyed like browser refresh, tab closed, etc...
        window.addEventListener('beforeunload', () => {
            this.Save();
            return false;
        });
    }

    //----------------------------------------------------------------------
    KeepMediaPlaying: boolean = true;
    ContinuousPlaying: boolean = true;
    PlayWhileHidden: boolean = true;
...
Load() {
        console.log("UserOptionsService------------>>Load");
        this.Storage.ready().then(() => {
            this.Storage.get("KeepMediaPlaying").then((data) => {
                this.KeepMediaPlaying = data;
                console.log("UserOptionsService<<------------Storage.get ", data);
            })
                .catch(() => {
                    console.log("UserOptionsService------------>>Load DEFAULTS");
                    this.KeepMediaPlaying = true;
                });

            this.Storage.get("ContinuousPlaying").then((data) => {
                this.ContinuousPlaying = data;
                console.log("UserOptionsService<<------------Storage.get ", data);
            })
                .catch(() => {
                    console.log("UserOptionsService------------>>Load DEFAULTS");
                    this.ContinuousPlaying = false;
                });

        });
    }

So I attempted to factor out the Storage.get like the following:

 private StorageGet(Key: string, Default: any): any {
        this.Storage.get(Key).then((data) => {
            console.log("UserOptionsService<<------------Storage.get ", Key, data);
            return (data);
        })
            .catch(() => {
                console.log("UserOptionsService------------>>Load DEFAULTS", Default);
                return Default;
            });
    }

and call my StorageGet() like so

  Load() {
        console.log("UserOptionsService------------>>Load");
        this.Storage.ready().then(() => {

            this.KeepMediaPlaying = this.StorageGet("KeepMediaPlaying", true);
            this.ContinuousPlaying = this.StorageGet("ContinuousPlaying", true);
            this.PlayWhileHidden = this.StorageGet("PlayWhileHidden ", false);
        });
    }

However, after the change above, when I enter my settings view of toggles they do not reflect the loaded values from storage until the second visit.
I don’t know how to to apply the Promise.All( …) to my case.
Can you please assist me in coding the “promises/resolve” correctly? Just when I think I understand them, they bite me again, I am lost :slight_smile:

The view component does just the following

 // 20170515
    ionViewWillEnter() {
        console.log("UserOptions<<------------ionViewWillEnter");
        this.UserOptionsService.Load();
    }
    // 20170515    
    ionViewDidLeave() {
        console.log("UserOptions<<------------ionViewDidLeave");
        this.UserOptionsService.Save();
    }

and the view html (template) looks like this:

        <ion-item>
            <ion-label>Keep Media Playing</ion-label>
            <ion-toggle [(ngModel)]="UserOptionsService.KeepMediaPlaying"></ion-toggle>
        </ion-item>

        <ion-item>
            <ion-label>Continuous Playing</ion-label>
            <ion-toggle [(ngModel)]="UserOptionsService.ContinuousPlaying"></ion-toggle>
        </ion-item>

        <ion-item>
            <ion-label>Play While Hidden</ion-label>
            <ion-toggle [(ngModel)]="UserOptionsService.PlayWhileHidden"></ion-toggle>
        </ion-item>

Thank you.

** NOTE: ** every app developer will sooner or later deal with storage, if you happen to know of a pattern, starter or template, we could all follow it, it would cut on the noise multi posts such trivial topics generate. By all means, please include a link.

While I wait for the darn pizza to be prepared…
How do I capture ALL promises?
When ALL promises are resolved, how do I tell the view to refresh itself?

  • via posting an event from the service to the page?
  • how will the view refresh its toggles controls upon subscribe to such an event?
  • Isn’t ngModel in the view supposed to take care of that?

Thanks.


#18

While I could not wait for that promised pizza, I cooked up a different pizza

Like so

    private StorageGet(Key: string, Default: any) {

        return new Promise((resolve, reject) => {
            this.Storage.get(Key).then((data) => {
                console.log("UserOptionsService<<------------Storage.get ", Key, data);
                resolve(data);
            })
                .catch(() => {
                    console.log("UserOptionsService------------>>Load DEFAULTS", Default);
                    resolve(Default);
                });
        });
    }

and call it like this

    Load() {
        console.log("UserOptionsService------------>>Load");
        this.Storage.ready().then(() => {
this.StorageGet("KeepMediaPlaying", true).then((data:boolean) => {this.KeepMediaPlaying = data});
            this.StorageGet("ContinuousPlaying", true).then((data:boolean) => {this.ContinuousPlaying = data});
                
        });
    }

Even though it appear to be causing the view load the toggles matching the loaded value from the storage (dumped to console), IOW, it’s working, I don’t know what I am doing :slight_smile: just hacking away…

I have also tried pure guess something like this, which also seems to work

            Promise.all([
                this.StorageGet("KeepMediaPlaying", true).then((data: boolean) => { this.KeepMediaPlaying = data })
                , this.StorageGet("ContinuousPlaying", true).then((data: boolean) => { this.ContinuousPlaying = data })
            ]);

Ideally, I would like to have a boolean flag in the above service let’s say “Ready” on the which the view can hide the toggles and switch to a spinner with a couple of *ngIf, in case the view races and paints before the service gets to load the data from whatever storage.


#19

Ok, here is a late night pizza… and it is working

            Promise.all([
                this.StorageGet("KeepMediaPlaying", true).then((data: boolean) => { this.KeepMediaPlaying = data })
                , this.StorageGet("ContinuousPlaying", true).then((data: boolean) => { this.ContinuousPlaying = data })
            ])
                .then(() => { this.Ready = true });

the view does this…it shows a spinner in case the service is slowly roasting that pizza to an extra crisp…

<!---->
<ion-content padding>
    <!--http://ionicframework.com/docs/api/components/spinner/Spinner/ -->
    <ion-spinner *ngIf="!UserOptionsService.Ready" name="bubbles"></ion-spinner>
    <div *ngIf="UserOptionsService.Ready">

        <!--http://ionicframework.com/docs/api/components/toolbar/Toolbar/ -->
        <!--http://ionicframework.com/docs/components/#toggle -->
        <!--https://webcake.co/binding-data-to-toggles-and-checkboxes-in-ionic-2/ -->
        <!--https://forum.ionicframework.com/t/ionic-2-beta9-toggle-ionchange/55535 -->
        <ion-item>
            <ion-label>Keep Media Playing</ion-label>
            <ion-toggle [(ngModel)]="UserOptionsService.KeepMediaPlaying"></ion-toggle>
        </ion-item>

        <ion-item>
            <ion-label>Continuous Playing</ion-label>
            <ion-toggle [(ngModel)]="UserOptionsService.ContinuousPlaying"></ion-toggle>
        </ion-item>
    </div>
</ion-content>

I hope this is what I am supposed to be coding like ?-(
BTW, I faked a delay in my StorageGet() and I get the expected behavior and results of the view.
So Promise.All( ) is doing a parallel wait on all the calls and returns when all resolve or reject.

Now, I need to fix, handle that unexpected browser refresh, close tab, thingy…
How, can I avoid doing that kind of handling in every view if service is not the place?
Have all views derive from a base class? Please raise your hand, before I cook another pizza!


#20

Is there a reason you’re splitting all these things up? I would just do this:

export interface Preferences {
  playContinuously: boolean;
  keepMediaPlaying: boolean;
  playWhileHidden: boolean;
}

export class PreferenceService {
  preferences: Promise<Preferences>;

  constructor(private _storage: Storage) {
    this.preferences = _storage.ready().then(() => _storage.get('preferences'));
  }

  setPreferences(prefs: Preferences): Promise<void> {
    return this._storage.set('preferences', prefs).then(() => {
      this.preferences = Promise.resolve(prefs);
    });
  }
}

export class SettingsPage {
  preferences: Preferences;

  constructor(private _prefService: PreferenceService) {
    _prefService.preferences.then(prefs => this.preferences = prefs);
  }

  save(): void {
    this._prefService.setPreferences(this.preferences);
  }
}

Synchronization between different components
Provider/server: Provide the data or contain the data?