2017-05-03 15:35:00 +02:00
/ * *
* Copyright 2013 - present , Facebook , Inc .
* All rights reserved .
*
* This source code is licensed under the BSD - style license found in the
* LICENSE file in the root directory of this source tree . An additional grant
* of patent rights can be found in the PATENTS file in the same directory .
*
*
* /
'use strict' ;
var _prodInvariant = require ( './reactProdInvariant' ) ;
var invariant = require ( 'fbjs/lib/invariant' ) ;
var OBSERVED _ERROR = { } ;
/ * *
* ` Transaction ` creates a black box that is able to wrap any method such that
* certain invariants are maintained before and after the method is invoked
* ( Even if an exception is thrown while invoking the wrapped method ) . Whoever
* instantiates a transaction can provide enforcers of the invariants at
* creation time . The ` Transaction ` class itself will supply one additional
* automatic invariant for you - the invariant that any transaction instance
* should not be run while it is already being run . You would typically create a
* single instance of a ` Transaction ` for reuse multiple times , that potentially
* is used to wrap several different methods . Wrappers are extremely simple -
* they only require implementing two methods .
*
* < pre >
* wrappers ( injected at creation time )
* + +
* | |
* + -- -- -- -- -- -- -- -- - | -- -- -- -- | -- -- -- -- -- -- -- +
* | v | |
* | + -- -- -- -- -- -- -- - + | |
* | + -- | wrapper1 | -- - | -- -- + |
* | | + -- -- -- -- -- -- -- - + v | |
* | | + -- -- -- -- -- -- - + | |
* | | + -- -- | wrapper2 | -- -- -- -- + |
* | | | + -- -- -- -- -- -- - + | | |
* | | | | | |
* | v v v v | wrapper
* | + -- - + + -- - + + -- -- -- -- - + + -- - + + -- - + | invariants
* perform ( anyMethod ) | | | | | | | | | | | | maintained
* + -- -- -- -- -- -- -- -- - > | - | -- - | - | -- - | -- > | anyMethod | -- - | -- - | - | -- - | - | -- -- -- -- >
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | + -- - + + -- - + + -- -- -- -- - + + -- - + + -- - + |
* | initialize close |
* + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - +
* < / p r e >
*
* Use cases :
* - Preserving the input selection ranges before / after reconciliation .
* Restoring selection even in the event of an unexpected error .
* - Deactivating events while rearranging the DOM , preventing blurs / focuses ,
* while guaranteeing that afterwards , the event system is reactivated .
* - Flushing a queue of collected DOM mutations to the main UI thread after a
* reconciliation takes place in a worker thread .
* - Invoking any collected ` componentDidUpdate ` callbacks after rendering new
* content .
* - ( Future use case ) : Wrapping particular flushes of the ` ReactWorker ` queue
* to preserve the ` scrollTop ` ( an automatic scroll aware DOM ) .
* - ( Future use case ) : Layout calculations before and after DOM updates .
*
* Transactional plugin API :
* - A module that has an ` initialize ` method that returns any precomputation .
* - and a ` close ` method that accepts the precomputation . ` close ` is invoked
* when the wrapped process is completed , or has failed .
*
* @ param { Array < TransactionalWrapper > } transactionWrapper Wrapper modules
* that implement ` initialize ` and ` close ` .
* @ return { Transaction } Single transaction for reuse in thread .
*
* @ class Transaction
* /
var TransactionImpl = {
/ * *
* Sets up this instance so that it is prepared for collecting metrics . Does
* so such that this setup method may be used on an instance that is already
* initialized , in a way that does not consume additional memory upon reuse .
* That can be useful if you decide to make your subclass of this mixin a
* "PooledClass" .
* /
reinitializeTransaction : function ( ) {
this . transactionWrappers = this . getTransactionWrappers ( ) ;
if ( this . wrapperInitData ) {
this . wrapperInitData . length = 0 ;
} else {
this . wrapperInitData = [ ] ;
}
this . _isInTransaction = false ;
} ,
_isInTransaction : false ,
/ * *
* @ abstract
* @ return { Array < TransactionWrapper > } Array of transaction wrappers .
* /
getTransactionWrappers : null ,
isInTransaction : function ( ) {
return ! ! this . _isInTransaction ;
} ,
2017-08-14 05:01:11 +02:00
/* eslint-disable space-before-function-paren */
2017-05-03 15:35:00 +02:00
/ * *
* Executes the function within a safety window . Use this for the top level
* methods that result in large amounts of computation / mutations that would
* need to be safety checked . The optional arguments helps prevent the need
* to bind in many cases .
*
* @ param { function } method Member of scope to call .
* @ param { Object } scope Scope to invoke from .
* @ param { Object ? = } a Argument to pass to the method .
* @ param { Object ? = } b Argument to pass to the method .
* @ param { Object ? = } c Argument to pass to the method .
* @ param { Object ? = } d Argument to pass to the method .
* @ param { Object ? = } e Argument to pass to the method .
* @ param { Object ? = } f Argument to pass to the method .
*
* @ return { * } Return value from ` method ` .
* /
perform : function ( method , scope , a , b , c , d , e , f ) {
2017-08-14 05:01:11 +02:00
/* eslint-enable space-before-function-paren */
2017-05-03 15:35:00 +02:00
! ! 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 ;
var errorThrown ;
var ret ;
try {
this . _isInTransaction = true ;
// Catching errors makes debugging more difficult, so we start with
// errorThrown set to true before setting it to false after calling
// close -- if it's still set to true in the finally block, it means
// one of these calls threw.
errorThrown = true ;
this . initializeAll ( 0 ) ;
ret = method . call ( scope , a , b , c , d , e , f ) ;
errorThrown = false ;
} finally {
try {
if ( errorThrown ) {
// If `method` throws, prefer to show that stack trace over any thrown
// by invoking `closeAll`.
try {
this . closeAll ( 0 ) ;
} catch ( err ) { }
} else {
// Since `method` didn't throw, we don't want to silence the exception
// here.
this . closeAll ( 0 ) ;
}
} finally {
this . _isInTransaction = false ;
}
}
return ret ;
} ,
initializeAll : function ( startIndex ) {
var transactionWrappers = this . transactionWrappers ;
for ( var i = startIndex ; i < transactionWrappers . length ; i ++ ) {
var wrapper = transactionWrappers [ i ] ;
try {
// Catching errors makes debugging more difficult, so we start with the
// OBSERVED_ERROR state before overwriting it with the real return value
// of initialize -- if it's still set to OBSERVED_ERROR in the finally
// block, it means wrapper.initialize threw.
this . wrapperInitData [ i ] = OBSERVED _ERROR ;
this . wrapperInitData [ i ] = wrapper . initialize ? wrapper . initialize . call ( this ) : null ;
} finally {
if ( this . wrapperInitData [ i ] === OBSERVED _ERROR ) {
// The initializer for wrapper i threw an error; initialize the
// remaining wrappers but silence any exceptions from them to ensure
// that the first error is the one to bubble up.
try {
this . initializeAll ( i + 1 ) ;
} catch ( err ) { }
}
}
}
} ,
/ * *
* Invokes each of ` this.transactionWrappers.close[i] ` functions , passing into
* them the respective return values of ` this.transactionWrappers.init[i] `
* ( ` close ` rs that correspond to initializers that failed will not be
* invoked ) .
* /
closeAll : function ( startIndex ) {
! this . isInTransaction ( ) ? process . env . NODE _ENV !== 'production' ? invariant ( false , 'Transaction.closeAll(): Cannot close transaction when none are open.' ) : _prodInvariant ( '28' ) : void 0 ;
var transactionWrappers = this . transactionWrappers ;
for ( var i = startIndex ; i < transactionWrappers . length ; i ++ ) {
var wrapper = transactionWrappers [ i ] ;
var initData = this . wrapperInitData [ i ] ;
var errorThrown ;
try {
// Catching errors makes debugging more difficult, so we start with
// errorThrown set to true before setting it to false after calling
// close -- if it's still set to true in the finally block, it means
// wrapper.close threw.
errorThrown = true ;
if ( initData !== OBSERVED _ERROR && wrapper . close ) {
wrapper . close . call ( this , initData ) ;
}
errorThrown = false ;
} finally {
if ( errorThrown ) {
// The closer for wrapper i threw an error; close the remaining
// wrappers but silence any exceptions from them to ensure that the
// first error is the one to bubble up.
try {
this . closeAll ( i + 1 ) ;
} catch ( e ) { }
}
}
}
this . wrapperInitData . length = 0 ;
}
} ;
module . exports = TransactionImpl ;