From 9b317d776a3af83badd85ba3b9ce232b9205deb2 Mon Sep 17 00:00:00 2001 From: Mehul Kar Date: Thu, 9 Jan 2020 16:51:47 -0800 Subject: [PATCH 1/3] Promise based initializers --- text/0000-promise-based-initializers.md | 86 +++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 text/0000-promise-based-initializers.md diff --git a/text/0000-promise-based-initializers.md b/text/0000-promise-based-initializers.md new file mode 100644 index 0000000000..16770053e3 --- /dev/null +++ b/text/0000-promise-based-initializers.md @@ -0,0 +1,86 @@ +- Start Date: 2020-01-09 +- Relevant Team(s): Ember.js +- RFC PR: (after opening the RFC PR, update this with a link to it and update the file name) +- Tracking: (leave this empty) + +# Promise-based Initializers + +## Summary + +This RFC proposes the ability to return a promise from an initializer or instance-initializer +and pause application boot until that promise resolves. + +## Motivation + +Currently, the earliest place that developers can write asynchronous code is in the ApplicationRoute. +For some use cases, this is not early enough, and it is useful to be able to run asynchronous code +before the application begins routing. + +For example, when booting an Ember application in an embedded environment, it may be necessary to +listen for events from the environment to determine the initial state to determine the initial URL. + +Another example may be to interact with 3rd party libraries that need to be initialized early but +have an asynchronous API. + +## Detailed Design + +The `Application` and `ApplicationInstance` (extending from `Engine` and `EngineInstance` +classes respectively) are responsible for running initializers and instance initializers. Each +implements a method that loops through the loaded initializers and executes the exported function. + +Initializer and Instance Initializer files each export an object with an `initialize` property +that references a function. For backwards compatibility, an `async` property should be added, and +should default to `false` if omitted. If true (or truthy), the Application/ApplicationInstance would +expect a promise to be returned from the function and wait for the promise to resolve +before running the next initializer in the queue. The value resolved from the promise would be +ignored. Promises that reject would throw an exception in development and be caught in production +builds. + +For example: + +```js +// app/initializers/foo.js +export function initialize() { + return Promise.resolve(); +} + +export default { + initialize, + async: true +}; +``` + +For maximum backwards compatibility, the boot sequence that calls these initializers could implement +an early check to see if any async initializers are defined and forgo all async handling. + +If implemented, future RFCs could: + +- change the default value of the `async` property to `true`. +- deprecate `deferReadiness` and `advanceReadiness` entirely. + +## How we teach this + +1. The guides would need to document the `async` key of the exported object from initializers and instance-initializers. +2. Some advanced logging to see when initializer functions are called may be useful. +3. Some information about how this relates to `deferReadiness` and `advanceReadiness` would be useful. +It's possible that deprecating these APIs as part of this RFC is appropriate, so there's a clear +signal that one is preferred over the other. + +## Drawbacks + +- There could be some performance concerns over introducing promises at boot time, but because this +is an opt-in feature, they can be avoided. + +## **Alternatives** + +- The existing `{defer|advance}Readiness` APIs could be introduced for instance-initializers as well. +Although promise-based async isn't exactly the same as this API, and there could be subtle differences +between both, it is unlikely that end-users would actually end up caring. + +## **Unresolved questions** + +- What happens if an initializer is marked as async but doesn't return a promise? +- What happens if a promise is created in an async initializer but not returned? +- Does this affect the algorithm that orders initializers based on `before` and `after` APIs? +- Does this affect the `boot` vs `_bootSync` of Application and ApplicationInstance at all and +should that be cleaned up *before* attempting this? From dd16ce70416ce011b5d78ea29047197204aca02e Mon Sep 17 00:00:00 2001 From: Mehul Kar Date: Thu, 9 Jan 2020 17:10:13 -0800 Subject: [PATCH 2/3] Add PR link --- text/0000-promise-based-initializers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-promise-based-initializers.md b/text/0000-promise-based-initializers.md index 16770053e3..3906de65ac 100644 --- a/text/0000-promise-based-initializers.md +++ b/text/0000-promise-based-initializers.md @@ -1,6 +1,6 @@ - Start Date: 2020-01-09 - Relevant Team(s): Ember.js -- RFC PR: (after opening the RFC PR, update this with a link to it and update the file name) +- RFC PR: https://github.com/emberjs/rfcs/pull/572 - Tracking: (leave this empty) # Promise-based Initializers From f1c8aa90985353d81a0bb2978e8cdbd5f700983f Mon Sep 17 00:00:00 2001 From: Mehul Kar Date: Wed, 5 Aug 2020 10:42:33 -0700 Subject: [PATCH 3/3] attempt to better describe motivations --- text/0000-promise-based-initializers.md | 47 +++++++++++++++---------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/text/0000-promise-based-initializers.md b/text/0000-promise-based-initializers.md index 3906de65ac..933d6c9c5f 100644 --- a/text/0000-promise-based-initializers.md +++ b/text/0000-promise-based-initializers.md @@ -12,15 +12,24 @@ and pause application boot until that promise resolves. ## Motivation -Currently, the earliest place that developers can write asynchronous code is in the ApplicationRoute. -For some use cases, this is not early enough, and it is useful to be able to run asynchronous code -before the application begins routing. - -For example, when booting an Ember application in an embedded environment, it may be necessary to -listen for events from the environment to determine the initial state to determine the initial URL. - -Another example may be to interact with 3rd party libraries that need to be initialized early but -have an asynchronous API. +**Earlier Async Hooks** + +Currently, the earliest place developers can write blocking asynchronous code is +in the ApplicationRoute's `beforeModel()` hook. There are two problems with this: + +1. Addons cannot easily add behavior to this hook. To do so, they must reopen the +`Ember.Route` class, and then require that all Routes in the application remember +to call `super`. This approach does not scale well, but will also become harder +as Ember.Route moves away from extending from `CoreObject`, and thus losing the +`.reopen()` API. +1. Semantically, code can make more _sense_ to belong in the application lifecycle, +rather than the routing stack. For example, if your Ember application depends on an +embedded environment (an Electron shell or even an iframe), it may make more sense to +implement blocking code _before_ hitting the routing stack. This can also translate +to technical wins, as initializers run before routing starts, so, for example, +an initializer could determine the starting URL of the application (much like +window.location does today). Yet another example of better codifying developer patterns +is the ability to import or initialize 3rd part libraries with async APIs. ## Detailed Design @@ -41,12 +50,12 @@ For example: ```js // app/initializers/foo.js export function initialize() { - return Promise.resolve(); + return Promise.resolve(); } export default { - initialize, - async: true + initialize, + async: true, }; ``` @@ -63,19 +72,21 @@ If implemented, future RFCs could: 1. The guides would need to document the `async` key of the exported object from initializers and instance-initializers. 2. Some advanced logging to see when initializer functions are called may be useful. 3. Some information about how this relates to `deferReadiness` and `advanceReadiness` would be useful. -It's possible that deprecating these APIs as part of this RFC is appropriate, so there's a clear -signal that one is preferred over the other. + It's possible that deprecating these APIs as part of this RFC is appropriate, so there's a clear + signal that one is preferred over the other. ## Drawbacks - There could be some performance concerns over introducing promises at boot time, but because this -is an opt-in feature, they can be avoided. + is an opt-in feature, they can be avoided. ## **Alternatives** - The existing `{defer|advance}Readiness` APIs could be introduced for instance-initializers as well. -Although promise-based async isn't exactly the same as this API, and there could be subtle differences -between both, it is unlikely that end-users would actually end up caring. + Although promise-based async isn't exactly the same as this API, and there could be subtle differences + between both, it is unlikely that end-users would actually end up caring. +- Deprecate initializers entirely and create new primitives for hooking into the +application boot lifecycle using the `Application` class. ## **Unresolved questions** @@ -83,4 +94,4 @@ between both, it is unlikely that end-users would actually end up caring. - What happens if a promise is created in an async initializer but not returned? - Does this affect the algorithm that orders initializers based on `before` and `after` APIs? - Does this affect the `boot` vs `_bootSync` of Application and ApplicationInstance at all and -should that be cleaned up *before* attempting this? + should that be cleaned up _before_ attempting this?