-
Notifications
You must be signed in to change notification settings - Fork 68
Fix calls awaiting forever on firebase scripts failing to load on web #204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,8 @@ declare var window: any; | |
|
|
||
| export class FirebaseAnalyticsWeb | ||
| extends WebPlugin | ||
| implements FirebaseAnalyticsPlugin { | ||
| implements FirebaseAnalyticsPlugin | ||
| { | ||
| private not_supported_mssg = "This method is not supported"; | ||
| private options_missing_mssg = "Firebase options are missing"; | ||
| private duplicate_app_mssg = "Firebase app already exists"; | ||
|
|
@@ -15,6 +16,7 @@ export class FirebaseAnalyticsWeb | |
|
|
||
| public readonly ready: Promise<any>; | ||
| private readyResolver: Function; | ||
| private readyReject: Function; | ||
| private analyticsRef: any; | ||
|
|
||
| private scripts = [ | ||
|
|
@@ -30,7 +32,11 @@ export class FirebaseAnalyticsWeb | |
|
|
||
| constructor() { | ||
| super(); | ||
| this.ready = new Promise((resolve) => (this.readyResolver = resolve)); | ||
| this.ready = new Promise( | ||
| (resolve, reject) => ( | ||
| (this.readyResolver = resolve), (this.readyReject = reject) | ||
| ), | ||
| ); | ||
| this.configure(); | ||
| } | ||
|
|
||
|
|
@@ -40,10 +46,9 @@ export class FirebaseAnalyticsWeb | |
| * @returns firebase analytics object reference | ||
| * Platform: Web | ||
| */ | ||
| initializeFirebase(options: FirebaseInitOptions): Promise<any> { | ||
| return new Promise(async (resolve, reject) => { | ||
| await this.ready; | ||
|
|
||
| async initializeFirebase(options: FirebaseInitOptions): Promise<any> { | ||
| await this.ready; | ||
| return new Promise((resolve, reject) => { | ||
| if (this.hasFirebaseInitialized()) { | ||
| reject(this.duplicate_app_mssg); | ||
| return; | ||
|
|
@@ -65,10 +70,9 @@ export class FirebaseAnalyticsWeb | |
| * @param options - userId: unique identifier of the user to log | ||
| * Platform: Web/Android/iOS | ||
| */ | ||
| setUserId(options: { userId: string }): Promise<void> { | ||
| return new Promise(async (resolve, reject) => { | ||
| await this.ready; | ||
|
|
||
| async setUserId(options: { userId: string }): Promise<void> { | ||
| await this.ready; | ||
| return new Promise((resolve, reject) => { | ||
| if (!this.analyticsRef) { | ||
| reject(this.analytics_missing_mssg); | ||
| return; | ||
|
|
@@ -92,10 +96,12 @@ export class FirebaseAnalyticsWeb | |
| * value: The value of the user property. | ||
| * Platform: Web/Android/iOS | ||
| */ | ||
| setUserProperty(options: { name: string; value: string }): Promise<void> { | ||
| return new Promise(async (resolve, reject) => { | ||
| await this.ready; | ||
|
|
||
| async setUserProperty(options: { | ||
| name: string; | ||
| value: string; | ||
| }): Promise<void> { | ||
| await this.ready; | ||
| return new Promise((resolve, reject) => { | ||
| if (!this.analyticsRef) { | ||
| reject(this.analytics_missing_mssg); | ||
| return; | ||
|
|
@@ -156,10 +162,9 @@ export class FirebaseAnalyticsWeb | |
| * params: the map of event parameters. | ||
| * Platform: Web/Android/iOS | ||
| */ | ||
| logEvent(options: { name: string; params: object }): Promise<void> { | ||
| return new Promise(async (resolve, reject) => { | ||
| await this.ready; | ||
|
|
||
| async logEvent(options: { name: string; params: object }): Promise<void> { | ||
| await this.ready; | ||
| return new Promise((resolve, reject) => { | ||
| if (!this.analyticsRef) { | ||
| reject(this.analytics_missing_mssg); | ||
| return; | ||
|
|
@@ -185,10 +190,9 @@ export class FirebaseAnalyticsWeb | |
| * @param options - enabled: boolean true/false to enable/disable logging | ||
| * Platform: Web/Android/iOS | ||
| */ | ||
| setCollectionEnabled(options: { enabled: boolean }): Promise<void> { | ||
| return new Promise(async (resolve, reject) => { | ||
| await this.ready; | ||
|
|
||
| async setCollectionEnabled(options: { enabled: boolean }): Promise<void> { | ||
| await this.ready; | ||
| return new Promise((resolve, reject) => { | ||
| if (!this.analyticsRef) { | ||
| reject(this.analytics_missing_mssg); | ||
| return; | ||
|
|
@@ -218,10 +222,9 @@ export class FirebaseAnalyticsWeb | |
| return this.analyticsRef; | ||
| } | ||
|
|
||
| enable(): Promise<void> { | ||
| return new Promise(async (resolve, reject) => { | ||
| await this.ready; | ||
|
|
||
| async enable(): Promise<void> { | ||
| await this.ready; | ||
| return new Promise((resolve, reject) => { | ||
| if (!this.analyticsRef) { | ||
| reject(this.analytics_missing_mssg); | ||
| return; | ||
|
|
@@ -232,10 +235,9 @@ export class FirebaseAnalyticsWeb | |
| }); | ||
| } | ||
|
|
||
| disable(): Promise<void> { | ||
| return new Promise(async (resolve, reject) => { | ||
| await this.ready; | ||
|
|
||
| async disable(): Promise<void> { | ||
| await this.ready; | ||
| return new Promise((resolve, reject) => { | ||
| if (!this.analyticsRef) { | ||
| reject(this.analytics_missing_mssg); | ||
| return; | ||
|
|
@@ -261,7 +263,8 @@ export class FirebaseAnalyticsWeb | |
| this.analyticsRef = window.firebase.analytics(); | ||
| } | ||
| } catch (error) { | ||
| throw error; | ||
| this.readyReject(error); | ||
| return; | ||
| } | ||
|
|
||
| const interval = setInterval(() => { | ||
|
|
@@ -289,12 +292,14 @@ export class FirebaseAnalyticsWeb | |
| return resolve(null); | ||
| } | ||
|
|
||
| await this.loadScript(firebaseAppScript.key, firebaseAppScript.src); | ||
| await this.loadScript( | ||
| firebaseAnalyticsScript.key, | ||
| firebaseAnalyticsScript.src | ||
| resolve( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need this change?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar reasoning here, using await will throw an exception on error which causes an unhandled promise rejection - instead resolving it using this promise propagates any error by rejecting the promise.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I was wrong about this actually and only the other change is necessary. Let me do some testing
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems it won't work without the change in my testing. Using async / await inside a new promise constructor is an anti-pattern because if it throws inside an async callback, the error isn't propagated correctly. |
||
| this.loadScript(firebaseAppScript.key, firebaseAppScript.src).then(() => | ||
| this.loadScript( | ||
| firebaseAnalyticsScript.key, | ||
| firebaseAnalyticsScript.src, | ||
| ), | ||
| ), | ||
| ); | ||
| resolve(null); | ||
| }); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this change?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error being thrown here means that the ready promise is never rejected, so if you do await FirebaseAnalytics.initialize() you can't catch the error using a try / catch, and the call awaits forever. This was causing an issue for me because I work on a web app that can be used offline, so I need to be able to handle his gracefully.