Reputation: 340
As an amateur PHP coder, I'm having a hard time grasping JS async behavior. I'm writing a Firefox WebExtension that uses two StorageAreas from the Storage API for setting/getting options, and I want to merge all options into one object to pass around the application efficiently:
'use strict';
class Options {
constructor() {
this.defaults = {};
this.locals = {};
this.options = {};
}
getDefaults() {
browser.storage.managed.get().then(res => {
this.defaults = res;
});
}
getLocals() {
browser.storage.local.get().then(res => {
this.locals = res;
});
}
get() {
this.getDefaults();
this.getLocals();
this.options = Object.assign(this.defaults, this.locals); // <-- This is where the code fails: this.defaults and this.locals are empty.
return this;
}
}
const opts = new Options().get();
console.log(opts); // Object { defaults: {}, locals: {}, options: {} } background.js:31:1
console.log(Object.keys(opts)); // Array(3) [ "defaults", "locals", "options" ] background.js:32:1
console.log(opts.defaults); // Object { } background.js:33:1
const options = Object.assign(opts.defaults, opts.locals);
console.log(options); // Object { } background.js:36:1
I have indicated the line where the error first triggers, and after banging my head against the same wall for 2+ days now, I believe it is either related to the async character of the Promise returned by Firefox's browser.storage.*.get(), or related to variable scopes.
I have tried:
Thx for any pointers - my mind is tired of reviewing/rewriting these 36 loc...
Upvotes: 1
Views: 1313
Reputation: 664297
Don't write asynchronous functions to initialise your instances. Instead, do the asynchronous work before constructing the objects, let the constructor take the data as parameters.
In your case, that's
class Options {
constructor(defaults = {}, locals = {}) {
this.defaults = defaults;
this.locals = locals;
this.options = Object.assign(this.defaults, this.locals);
}
// static methods that don't modify an instance, but return promises
static getDefaults() {
return browser.storage.managed.get();
}
static getLocals() {
return browser.storage.local.get();
}
// static method to load data and create an instance, returning a promise for it
static async get() {
const [defaults, locals] = await Promise.all([this.getDefaults(), this.getLocals()]);
return new this(defaults, locals);;
}
}
const options = Options.get();
console.log(options); // Promise { }
options.then(opts => {
console.log(opts); // Options { defaults: {…}, locals: {…}, options: {…} }
console.log(Object.keys(opts)); // Array(3) [ "defaults", "locals", "options" ]
console.log(opts.defaults); // Object { … }
});
Upvotes: 1
Reputation: 40842
You do not wait for the promises create by the calls this.getDefaults();
and this.getLocals();
so at the time you do this.options = Object.assign( ...
the data is not ready.
If you create a Promise in a function then you need to return from it so that the caller can wait for that Promise, to resolve.
getDefaults() {
return browser.storage.managed.get().then(res => {
this.defaults = res;
});
}
getLocals() {
return browser.storage.local.get().then(res => {
this.locals = res;
});
}
get() {
return Promise.all([
this.getDefaults(),
this.getLocals()
])
.then(() => {
this.options = Object.assign(this.defaults, this.locals);
return this
})
})
And you for sure also need to use then
or await
on get()
to wait for get
to be finished.
Upvotes: 1