|
1 | 1 | import * as url from 'url';
|
2 | 2 | import * as domain from 'domain';
|
3 | 3 | import * as domainContext from 'domain-context';
|
4 |
| -import { addTask } from './main'; |
5 | 4 | const isomorphicFetch = require('isomorphic-fetch');
|
6 | 5 | const isBrowser: boolean = (new Function('try { return this === window; } catch (e) { return false; }'))();
|
7 | 6 |
|
@@ -42,9 +41,33 @@ function issueRequest(baseUrl: string, req: string | Request, init?: RequestInit
|
42 | 41 | }
|
43 | 42 |
|
44 | 43 | export function fetch(url: string | Request, init?: RequestInit): Promise<any> {
|
45 |
| - const promise = issueRequest(baseUrl(), url, init); |
46 |
| - addTask(promise); |
47 |
| - return promise; |
| 44 | + // As of domain-task 2.0.0, we no longer auto-add the 'fetch' promise to the current domain task list. |
| 45 | + // This is because it's misleading to do so, and can result in race-condition bugs, e.g., |
| 46 | + // https://github.com/aspnet/JavaScriptServices/issues/166 |
| 47 | + // |
| 48 | + // Consider this usage: |
| 49 | + // |
| 50 | + // import { fetch } from 'domain-task/fetch'; |
| 51 | + // fetch(something).then(callback1).then(callback2) ...etc... .then(data => updateCriticalAppState); |
| 52 | + // |
| 53 | + // If we auto-add the very first 'fetch' promise to the domain task list, then the domain task completion |
| 54 | + // callback might fire at any point among all the chained callbacks. If there are enough chained callbacks, |
| 55 | + // it's likely to occur before the final 'updateCriticalAppState' one. Previously we thought it was enough |
| 56 | + // for domain-task to use setTimeout(..., 0) so that its action occurred after all synchronously-scheduled |
| 57 | + // chained promise callbacks, but this turns out not to be the case. Current versions of Node will run |
| 58 | + // setTimeout-scheduled callbacks *before* setImmediate ones, if their timeout has elapsed. So even if you |
| 59 | + // use setTimeout(..., 10), then this callback will run before setImmediate(...) if there were 10ms or more |
| 60 | + // of CPU-blocking activity. In other words, a race condition. |
| 61 | + // |
| 62 | + // The correct design is for the final chained promise to be the thing added to the domain task list, but |
| 63 | + // this can only be done by the developer and not baked into the 'fetch' API. The developer needs to write |
| 64 | + // something like: |
| 65 | + // |
| 66 | + // var myTask = fetch(something).then(callback1).then(callback2) ...etc... .then(data => updateCriticalAppState); |
| 67 | + // addDomainTask(myTask); |
| 68 | + // |
| 69 | + // ... so that the domain-tasks-completed callback never fires until after 'updateCriticalAppState'. |
| 70 | + return issueRequest(baseUrl(), url, init); |
48 | 71 | }
|
49 | 72 |
|
50 | 73 | export function baseUrl(url?: string): string {
|
|
0 commit comments