1 | /**
|
2 | * Copyright 2013-present, Facebook, Inc.
|
3 | * All rights reserved.
|
4 | *
|
5 | * This source code is licensed under the BSD-style license found in the
|
6 | * LICENSE file in the root directory of this source tree. An additional grant
|
7 | * of patent rights can be found in the PATENTS file in the same directory.
|
8 | *
|
9 | *
|
10 | */
|
11 |
|
12 | ;
|
13 |
|
14 | var _prodInvariant = require('./reactProdInvariant');
|
15 |
|
16 | var invariant = require('fbjs/lib/invariant');
|
17 |
|
18 | var OBSERVED_ERROR = {};
|
19 |
|
20 | /**
|
21 | * `Transaction` creates a black box that is able to wrap any method such that
|
22 | * certain invariants are maintained before and after the method is invoked
|
23 | * (Even if an exception is thrown while invoking the wrapped method). Whoever
|
24 | * instantiates a transaction can provide enforcers of the invariants at
|
25 | * creation time. The `Transaction` class itself will supply one additional
|
26 | * automatic invariant for you - the invariant that any transaction instance
|
27 | * should not be run while it is already being run. You would typically create a
|
28 | * single instance of a `Transaction` for reuse multiple times, that potentially
|
29 | * is used to wrap several different methods. Wrappers are extremely simple -
|
30 | * they only require implementing two methods.
|
31 | *
|
32 | * <pre>
|
33 | * wrappers (injected at creation time)
|
34 | * + +
|
35 | * | |
|
36 | * +-----------------|--------|--------------+
|
37 | * | v | |
|
38 | * | +---------------+ | |
|
39 | * | +--| wrapper1 |---|----+ |
|
40 | * | | +---------------+ v | |
|
41 | * | | +-------------+ | |
|
42 | * | | +----| wrapper2 |--------+ |
|
43 | * | | | +-------------+ | | |
|
44 | * | | | | | |
|
45 | * | v v v v | wrapper
|
46 | * | +---+ +---+ +---------+ +---+ +---+ | invariants
|
47 | * perform(anyMethod) | | | | | | | | | | | | maintained
|
48 | * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
|
49 | * | | | | | | | | | | | |
|
50 | * | | | | | | | | | | | |
|
51 | * | | | | | | | | | | | |
|
52 | * | +---+ +---+ +---------+ +---+ +---+ |
|
53 | * | initialize close |
|
54 | * +-----------------------------------------+
|
55 | * </pre>
|
56 | *
|
57 | * Use cases:
|
58 | * - Preserving the input selection ranges before/after reconciliation.
|
59 | * Restoring selection even in the event of an unexpected error.
|
60 | * - Deactivating events while rearranging the DOM, preventing blurs/focuses,
|
61 | * while guaranteeing that afterwards, the event system is reactivated.
|
62 | * - Flushing a queue of collected DOM mutations to the main UI thread after a
|
63 | * reconciliation takes place in a worker thread.
|
64 | * - Invoking any collected `componentDidUpdate` callbacks after rendering new
|
65 | * content.
|
66 | * - (Future use case): Wrapping particular flushes of the `ReactWorker` queue
|
67 | * to preserve the `scrollTop` (an automatic scroll aware DOM).
|
68 | * - (Future use case): Layout calculations before and after DOM updates.
|
69 | *
|
70 | * Transactional plugin API:
|
71 | * - A module that has an `initialize` method that returns any precomputation.
|
72 | * - and a `close` method that accepts the precomputation. `close` is invoked
|
73 | * when the wrapped process is completed, or has failed.
|
74 | *
|
75 | * @param {Array<TransactionalWrapper>} transactionWrapper Wrapper modules
|
76 | * that implement `initialize` and `close`.
|
77 | * @return {Transaction} Single transaction for reuse in thread.
|
78 | *
|
79 | * @class Transaction
|
80 | */
|
81 | var TransactionImpl = {
|
82 | /**
|
83 | * Sets up this instance so that it is prepared for collecting metrics. Does
|
84 | * so such that this setup method may be used on an instance that is already
|
85 | * initialized, in a way that does not consume additional memory upon reuse.
|
86 | * That can be useful if you decide to make your subclass of this mixin a
|
87 | * "PooledClass".
|
88 | */
|
89 | reinitializeTransaction: function () {
|
90 | this.transactionWrappers = this.getTransactionWrappers();
|
91 | if (this.wrapperInitData) {
|
92 | this.wrapperInitData.length = 0;
|
93 | } else {
|
94 | this.wrapperInitData = [];
|
95 | }
|
96 | this._isInTransaction = false;
|
97 | },
|
98 |
|
99 | _isInTransaction: false,
|
100 |
|
101 | /**
|
102 | * @abstract
|
103 | * @return {Array<TransactionWrapper>} Array of transaction wrappers.
|
104 | */
|
105 | getTransactionWrappers: null,
|
106 |
|
107 | isInTransaction: function () {
|
108 | return !!this._isInTransaction;
|
109 | },
|
110 |
|
111 | /* eslint-disable space-before-function-paren */
|
112 |
|
113 | /**
|
114 | * Executes the function within a safety window. Use this for the top level
|
115 | * methods that result in large amounts of computation/mutations that would
|
116 | * need to be safety checked. The optional arguments helps prevent the need
|
117 | * to bind in many cases.
|
118 | *
|
119 | * @param {function} method Member of scope to call.
|
120 | * @param {Object} scope Scope to invoke from.
|
121 | * @param {Object?=} a Argument to pass to the method.
|
122 | * @param {Object?=} b Argument to pass to the method.
|
123 | * @param {Object?=} c Argument to pass to the method.
|
124 | * @param {Object?=} d Argument to pass to the method.
|
125 | * @param {Object?=} e Argument to pass to the method.
|
126 | * @param {Object?=} f Argument to pass to the method.
|
127 | *
|
128 | * @return {*} Return value from `method`.
|
129 | */
|
130 | perform: function (method, scope, a, b, c, d, e, f) {
|
131 | /* eslint-enable space-before-function-paren */
|
132 | !!this.isInTransaction() ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Transaction.perform(...): Cannot initialize a transaction when there is already an outstanding transaction.') : _prodInvariant('27') : void 0;
|
133 | var errorThrown;
|
134 | var ret;
|
135 | try {
|
136 | this._isInTransaction = true;
|
137 | // Catching errors makes debugging more difficult, so we start with
|
138 | // errorThrown set to true before setting it to false after calling
|
139 | // close -- if it's still set to true in the finally block, it means
|
140 | // one of these calls threw.
|
141 | errorThrown = true;
|
142 | this.initializeAll(0);
|
143 | ret = method.call(scope, a, b, c, d, e, f);
|
144 | errorThrown = false;
|
145 | } finally {
|
146 | try {
|
147 | if (errorThrown) {
|
148 | // If `method` throws, prefer to show that stack trace over any thrown
|
149 | // by invoking `closeAll`.
|
150 | try {
|
151 | this.closeAll(0);
|
152 | } catch (err) {}
|
153 | } else {
|
154 | // Since `method` didn't throw, we don't want to silence the exception
|
155 | // here.
|
156 | this.closeAll(0);
|
157 | }
|
158 | } finally {
|
159 | this._isInTransaction = false;
|
160 | }
|
161 | }
|
162 | return ret;
|
163 | },
|
164 |
|
165 | initializeAll: function (startIndex) {
|
166 | var transactionWrappers = this.transactionWrappers;
|
167 | for (var i = startIndex; i < transactionWrappers.length; i++) {
|
168 | var wrapper = transactionWrappers[i];
|
169 | try {
|
170 | // Catching errors makes debugging more difficult, so we start with the
|
171 | // OBSERVED_ERROR state before overwriting it with the real return value
|
172 | // of initialize -- if it's still set to OBSERVED_ERROR in the finally
|
173 | // block, it means wrapper.initialize threw.
|
174 | this.wrapperInitData[i] = OBSERVED_ERROR;
|
175 | this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null;
|
176 | } finally {
|
177 | if (this.wrapperInitData[i] === OBSERVED_ERROR) {
|
178 | // The initializer for wrapper i threw an error; initialize the
|
179 | // remaining wrappers but silence any exceptions from them to ensure
|
180 | // that the first error is the one to bubble up.
|
181 | try {
|
182 | this.initializeAll(i + 1);
|
183 | } catch (err) {}
|
184 | }
|
185 | }
|
186 | }
|
187 | },
|
188 |
|
189 | /**
|
190 | * Invokes each of `this.transactionWrappers.close[i]` functions, passing into
|
191 | * them the respective return values of `this.transactionWrappers.init[i]`
|
192 | * (`close`rs that correspond to initializers that failed will not be
|
193 | * invoked).
|
194 | */
|
195 | closeAll: function (startIndex) {
|
196 | !this.isInTransaction() ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Transaction.closeAll(): Cannot close transaction when none are open.') : _prodInvariant('28') : void 0;
|
197 | var transactionWrappers = this.transactionWrappers;
|
198 | for (var i = startIndex; i < transactionWrappers.length; i++) {
|
199 | var wrapper = transactionWrappers[i];
|
200 | var initData = this.wrapperInitData[i];
|
201 | var errorThrown;
|
202 | try {
|
203 | // Catching errors makes debugging more difficult, so we start with
|
204 | // errorThrown set to true before setting it to false after calling
|
205 | // close -- if it's still set to true in the finally block, it means
|
206 | // wrapper.close threw.
|
207 | errorThrown = true;
|
208 | if (initData !== OBSERVED_ERROR && wrapper.close) {
|
209 | wrapper.close.call(this, initData);
|
210 | }
|
211 | errorThrown = false;
|
212 | } finally {
|
213 | if (errorThrown) {
|
214 | // The closer for wrapper i threw an error; close the remaining
|
215 | // wrappers but silence any exceptions from them to ensure that the
|
216 | // first error is the one to bubble up.
|
217 | try {
|
218 | this.closeAll(i + 1);
|
219 | } catch (e) {}
|
220 | }
|
221 | }
|
222 | }
|
223 | this.wrapperInitData.length = 0;
|
224 | }
|
225 | };
|
226 |
|
227 | module.exports = TransactionImpl; |
\ | No newline at end of file |