Repository: googlemaps/js-api-loader (ts)

Issue:
Calling the loader after google API has failed with x retries should try loading the library again
Relates to #93  and #99 

 **Is your feature request related to a problem? Please describe.**
Relates to #93
For a SPA this is useful as there is no page reload on navigation.

 **Describe the solution you'd like**
Similar to [this](https://github.com/googlemaps/js-api-loader/issues/93#issuecomment-727040227)

 **Describe alternatives you've considered**
N/A

 **Additional context**
N/A


Pull request:
feat: reset retry mechanism on failure with next loader
Fixes #101 🦕
Will not reset the retry mechanism until all retry attempts have been exhausted.

Patch:
diff --git a/package-lock.json b/package-lock.json
index 4c735df..ac6b2f8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4122,15 +4122,15 @@
       }
     },
     "@typescript-eslint/experimental-utils": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.4.0.tgz",
-      "integrity": "sha512-01+OtK/oWeSJTjQcyzDztfLF1YjvKpLFo+JZmurK/qjSRcyObpIecJ4rckDoRCSh5Etw+jKfdSzVEHevh9gJ1w==",
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.7.0.tgz",
+      "integrity": "sha512-cymzovXAiD4EF+YoHAB5Oh02MpnXjvyaOb+v+BdpY7lsJXZQN34oIETeUwVT2XfV9rSNpXaIcknDLfupO/tUoA==",
       "dev": true,
       "requires": {
         "@types/json-schema": "^7.0.3",
-        "@typescript-eslint/scope-manager": "4.4.0",
-        "@typescript-eslint/types": "4.4.0",
-        "@typescript-eslint/typescript-estree": "4.4.0",
+        "@typescript-eslint/scope-manager": "4.7.0",
+        "@typescript-eslint/types": "4.7.0",
+        "@typescript-eslint/typescript-estree": "4.7.0",
         "eslint-scope": "^5.0.0",
         "eslint-utils": "^2.0.0"
       },
@@ -4203,29 +4203,29 @@
       }
     },
     "@typescript-eslint/scope-manager": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.4.0.tgz",
-      "integrity": "sha512-r2FIeeU1lmW4K3CxgOAt8djI5c6Q/5ULAgdVo9AF3hPMpu0B14WznBAtxrmB/qFVbVIB6fSx2a+EVXuhSVMEyA==",
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.7.0.tgz",
+      "integrity": "sha512-ILITvqwDJYbcDCROj6+Ob0oCKNg3SH46iWcNcTIT9B5aiVssoTYkhKjxOMNzR1F7WSJkik4zmuqve5MdnA0DyA==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "4.4.0",
-        "@typescript-eslint/visitor-keys": "4.4.0"
+        "@typescript-eslint/types": "4.7.0",
+        "@typescript-eslint/visitor-keys": "4.7.0"
       }
     },
     "@typescript-eslint/types": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.4.0.tgz",
-      "integrity": "sha512-nU0VUpzanFw3jjX+50OTQy6MehVvf8pkqFcURPAE06xFNFenMj1GPEI6IESvp7UOHAnq+n/brMirZdR+7rCrlA==",
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.7.0.tgz",
+      "integrity": "sha512-uLszFe0wExJc+I7q0Z/+BnP7wao/kzX0hB5vJn4LIgrfrMLgnB2UXoReV19lkJQS1a1mHWGGODSxnBx6JQC3Sg==",
       "dev": true
     },
     "@typescript-eslint/typescript-estree": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.4.0.tgz",
-      "integrity": "sha512-Fh85feshKXwki4nZ1uhCJHmqKJqCMba+8ZicQIhNi5d5jSQFteWiGeF96DTjO8br7fn+prTP+t3Cz/a/3yOKqw==",
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.7.0.tgz",
+      "integrity": "sha512-5XZRQznD1MfUmxu1t8/j2Af4OxbA7EFU2rbo0No7meb46eHgGkSieFdfV6omiC/DGIBhH9H9gXn7okBbVOm8jw==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "4.4.0",
-        "@typescript-eslint/visitor-keys": "4.4.0",
+        "@typescript-eslint/types": "4.7.0",
+        "@typescript-eslint/visitor-keys": "4.7.0",
         "debug": "^4.1.1",
         "globby": "^11.0.1",
         "is-glob": "^4.0.1",
@@ -4263,12 +4263,12 @@
       }
     },
     "@typescript-eslint/visitor-keys": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.4.0.tgz",
-      "integrity": "sha512-oBWeroUZCVsHLiWRdcTXJB7s1nB3taFY8WGvS23tiAlT6jXVvsdAV4rs581bgdEjOhn43q6ro7NkOiLKu6kFqA==",
+      "version": "4.7.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.7.0.tgz",
+      "integrity": "sha512-aDJDWuCRsf1lXOtignlfiPODkzSxxop7D0rZ91L6ZuMlcMCSh0YyK+gAfo5zN/ih6WxMwhoXgJWC3cWQdaKC+A==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "4.4.0",
+        "@typescript-eslint/types": "4.7.0",
         "eslint-visitor-keys": "^2.0.0"
       },
       "dependencies": {
@@ -5519,9 +5519,9 @@
       }
     },
     "eslint-plugin-jest": {
-      "version": "24.1.0",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.1.0.tgz",
-      "integrity": "sha512-827YJ+E8B9PvXu/0eiVSNFfxxndbKv+qE/3GSMhdorCaeaOehtqHGX2YDW9B85TEOre9n/zscledkFW/KbnyGg==",
+      "version": "24.1.3",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.1.3.tgz",
+      "integrity": "sha512-dNGGjzuEzCE3d5EPZQ/QGtmlMotqnYWD/QpCZ1UuZlrMAdhG5rldh0N0haCvhGnUkSeuORS5VNROwF9Hrgn3Lg==",
       "dev": true,
       "requires": {
         "@typescript-eslint/experimental-utils": "^4.0.1"
@@ -9228,9 +9228,9 @@
       }
     },
     "rollup": {
-      "version": "2.33.1",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.33.1.tgz",
-      "integrity": "sha512-uY4O/IoL9oNW8MMcbA5hcOaz6tZTMIh7qJHx/tzIJm+n1wLoY38BLn6fuy7DhR57oNFLMbDQtDeJoFURt5933w==",
+      "version": "2.33.2",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.33.2.tgz",
+      "integrity": "sha512-QPQ6/fWCrzHtSXkI269rhKaC7qXGghYBwXU04b1JsDZ6ibZa3DJ9D1SFAYRMgx1inDg0DaTbb3N4Z1NK/r3fhw==",
       "dev": true,
       "requires": {
         "fsevents": "~2.1.2"
diff --git a/src/index.ts b/src/index.ts
index 4dd06c8..f8a4ed7 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -426,6 +426,15 @@ export class Loader {
     }
   }
 
+  private resetIfRetryingFailed(): void {
+    if (this.done && !this.loading && this.errors.length >= this.retries) {
+      this.deleteScript();
+      this.done = false;
+      this.loading = false;
+      this.errors = [];
+    }
+  }
+
   private loadErrorCallback(e: ErrorEvent): void {
     this.errors.push(e);
 
@@ -470,6 +479,7 @@ export class Loader {
       this.callback();
     }
 
+    this.resetIfRetryingFailed();
     if (this.done) {
       this.callback();
     } else {


Test patch:
diff --git a/src/index.test.ts b/src/index.test.ts
index 7774607..b0c8625 100644
--- a/src/index.test.ts
+++ b/src/index.test.ts
@@ -139,7 +139,9 @@ test("script onerror should reject promise with multiple loaders", async () => {
   expect(loader["done"]).toBeTruthy();
   expect(loader["loading"]).toBeFalsy();
   expect(loader["onerrorEvent"]).toBeInstanceOf(ErrorEvent);
+
   rejection = expect(extraLoader.load()).rejects.toBeInstanceOf(ErrorEvent);
+  loader["loadErrorCallback"](document.createEvent("ErrorEvent"));
 
   await rejection;
   expect(extraLoader["done"]).toBeTruthy();
@@ -165,6 +167,52 @@ test("script onerror should retry", async () => {
   expect(console.log).toHaveBeenCalledTimes(loader.retries);
 });
 
+test("script onerror should reset retry mechanism with next loader", async () => {
+  const loader = new Loader({ apiKey: "foo", retries: 1 });
+  const deleteScript = jest.spyOn(loader, "deleteScript");
+  // eslint-disable-next-line @typescript-eslint/no-empty-function
+  console.log = jest.fn();
+
+  let rejection = expect(loader.load()).rejects.toBeInstanceOf(ErrorEvent);
+  loader["loadErrorCallback"](document.createEvent("ErrorEvent"));
+  loader["loadErrorCallback"](document.createEvent("ErrorEvent"));
+  jest.runAllTimers();
+  await rejection;
+
+  rejection = expect(loader.load()).rejects.toBeInstanceOf(ErrorEvent);
+  expect(loader["done"]).toBeFalsy();
+  expect(loader["loading"]).toBeTruthy();
+  expect(loader["errors"].length).toBe(0);
+
+  loader["loadErrorCallback"](document.createEvent("ErrorEvent"));
+  loader["loadErrorCallback"](document.createEvent("ErrorEvent"));
+  jest.runAllTimers();
+
+  await rejection;
+  expect(deleteScript).toHaveBeenCalledTimes(3);
+  expect(console.log).toHaveBeenCalledTimes(loader.retries * 2);
+});
+
+test("script onerror should not reset retry mechanism with parallel loaders", async () => {
+  const loader = new Loader({ apiKey: "foo", retries: 1 });
+  const deleteScript = jest.spyOn(loader, "deleteScript");
+  // eslint-disable-next-line @typescript-eslint/no-empty-function
+  console.log = jest.fn();
+
+  const rejection1 = expect(loader.load()).rejects.toBeInstanceOf(ErrorEvent);
+  const rejection2 = expect(loader.load()).rejects.toBeInstanceOf(ErrorEvent);
+  loader["loadErrorCallback"](document.createEvent("ErrorEvent"));
+  loader["loadErrorCallback"](document.createEvent("ErrorEvent"));
+  jest.runAllTimers();
+
+  await Promise.all([rejection1, rejection2]);
+  expect(loader["done"]).toBeTruthy();
+  expect(loader["loading"]).toBeFalsy();
+  expect(loader["errors"].length).toBe(2);
+  expect(deleteScript).toHaveBeenCalledTimes(1);
+  expect(console.log).toHaveBeenCalledTimes(loader.retries);
+});
+
 test("singleton should be used", () => {
   const loader = new Loader({ apiKey: "foo" });
   const extraLoader = new Loader({ apiKey: "foo" });