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