react / react-0.13.3 / examples / basic-commonjs / node_modules / reactify / node_modules / react-tools / src / utils / Transaction.js
81152 views/**1* Copyright 2013-2014, Facebook, Inc.2* All rights reserved.3*4* This source code is licensed under the BSD-style license found in the5* LICENSE file in the root directory of this source tree. An additional grant6* of patent rights can be found in the PATENTS file in the same directory.7*8* @providesModule Transaction9*/1011"use strict";1213var invariant = require('invariant');1415/**16* `Transaction` creates a black box that is able to wrap any method such that17* certain invariants are maintained before and after the method is invoked18* (Even if an exception is thrown while invoking the wrapped method). Whoever19* instantiates a transaction can provide enforcers of the invariants at20* creation time. The `Transaction` class itself will supply one additional21* automatic invariant for you - the invariant that any transaction instance22* should not be run while it is already being run. You would typically create a23* single instance of a `Transaction` for reuse multiple times, that potentially24* is used to wrap several different methods. Wrappers are extremely simple -25* they only require implementing two methods.26*27* <pre>28* wrappers (injected at creation time)29* + +30* | |31* +-----------------|--------|--------------+32* | v | |33* | +---------------+ | |34* | +--| wrapper1 |---|----+ |35* | | +---------------+ v | |36* | | +-------------+ | |37* | | +----| wrapper2 |--------+ |38* | | | +-------------+ | | |39* | | | | | |40* | v v v v | wrapper41* | +---+ +---+ +---------+ +---+ +---+ | invariants42* perform(anyMethod) | | | | | | | | | | | | maintained43* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->44* | | | | | | | | | | | |45* | | | | | | | | | | | |46* | | | | | | | | | | | |47* | +---+ +---+ +---------+ +---+ +---+ |48* | initialize close |49* +-----------------------------------------+50* </pre>51*52* Use cases:53* - Preserving the input selection ranges before/after reconciliation.54* Restoring selection even in the event of an unexpected error.55* - Deactivating events while rearranging the DOM, preventing blurs/focuses,56* while guaranteeing that afterwards, the event system is reactivated.57* - Flushing a queue of collected DOM mutations to the main UI thread after a58* reconciliation takes place in a worker thread.59* - Invoking any collected `componentDidUpdate` callbacks after rendering new60* content.61* - (Future use case): Wrapping particular flushes of the `ReactWorker` queue62* to preserve the `scrollTop` (an automatic scroll aware DOM).63* - (Future use case): Layout calculations before and after DOM upates.64*65* Transactional plugin API:66* - A module that has an `initialize` method that returns any precomputation.67* - and a `close` method that accepts the precomputation. `close` is invoked68* when the wrapped process is completed, or has failed.69*70* @param {Array<TransactionalWrapper>} transactionWrapper Wrapper modules71* that implement `initialize` and `close`.72* @return {Transaction} Single transaction for reuse in thread.73*74* @class Transaction75*/76var Mixin = {77/**78* Sets up this instance so that it is prepared for collecting metrics. Does79* so such that this setup method may be used on an instance that is already80* initialized, in a way that does not consume additional memory upon reuse.81* That can be useful if you decide to make your subclass of this mixin a82* "PooledClass".83*/84reinitializeTransaction: function() {85this.transactionWrappers = this.getTransactionWrappers();86if (!this.wrapperInitData) {87this.wrapperInitData = [];88} else {89this.wrapperInitData.length = 0;90}91this._isInTransaction = false;92},9394_isInTransaction: false,9596/**97* @abstract98* @return {Array<TransactionWrapper>} Array of transaction wrappers.99*/100getTransactionWrappers: null,101102isInTransaction: function() {103return !!this._isInTransaction;104},105106/**107* Executes the function within a safety window. Use this for the top level108* methods that result in large amounts of computation/mutations that would109* need to be safety checked.110*111* @param {function} method Member of scope to call.112* @param {Object} scope Scope to invoke from.113* @param {Object?=} args... Arguments to pass to the method (optional).114* Helps prevent need to bind in many cases.115* @return Return value from `method`.116*/117perform: function(method, scope, a, b, c, d, e, f) {118invariant(119!this.isInTransaction(),120'Transaction.perform(...): Cannot initialize a transaction when there ' +121'is already an outstanding transaction.'122);123var errorThrown;124var ret;125try {126this._isInTransaction = true;127// Catching errors makes debugging more difficult, so we start with128// errorThrown set to true before setting it to false after calling129// close -- if it's still set to true in the finally block, it means130// one of these calls threw.131errorThrown = true;132this.initializeAll(0);133ret = method.call(scope, a, b, c, d, e, f);134errorThrown = false;135} finally {136try {137if (errorThrown) {138// If `method` throws, prefer to show that stack trace over any thrown139// by invoking `closeAll`.140try {141this.closeAll(0);142} catch (err) {143}144} else {145// Since `method` didn't throw, we don't want to silence the exception146// here.147this.closeAll(0);148}149} finally {150this._isInTransaction = false;151}152}153return ret;154},155156initializeAll: function(startIndex) {157var transactionWrappers = this.transactionWrappers;158for (var i = startIndex; i < transactionWrappers.length; i++) {159var wrapper = transactionWrappers[i];160try {161// Catching errors makes debugging more difficult, so we start with the162// OBSERVED_ERROR state before overwriting it with the real return value163// of initialize -- if it's still set to OBSERVED_ERROR in the finally164// block, it means wrapper.initialize threw.165this.wrapperInitData[i] = Transaction.OBSERVED_ERROR;166this.wrapperInitData[i] = wrapper.initialize ?167wrapper.initialize.call(this) :168null;169} finally {170if (this.wrapperInitData[i] === Transaction.OBSERVED_ERROR) {171// The initializer for wrapper i threw an error; initialize the172// remaining wrappers but silence any exceptions from them to ensure173// that the first error is the one to bubble up.174try {175this.initializeAll(i + 1);176} catch (err) {177}178}179}180}181},182183/**184* Invokes each of `this.transactionWrappers.close[i]` functions, passing into185* them the respective return values of `this.transactionWrappers.init[i]`186* (`close`rs that correspond to initializers that failed will not be187* invoked).188*/189closeAll: function(startIndex) {190invariant(191this.isInTransaction(),192'Transaction.closeAll(): Cannot close transaction when none are open.'193);194var transactionWrappers = this.transactionWrappers;195for (var i = startIndex; i < transactionWrappers.length; i++) {196var wrapper = transactionWrappers[i];197var initData = this.wrapperInitData[i];198var errorThrown;199try {200// Catching errors makes debugging more difficult, so we start with201// errorThrown set to true before setting it to false after calling202// close -- if it's still set to true in the finally block, it means203// wrapper.close threw.204errorThrown = true;205if (initData !== Transaction.OBSERVED_ERROR) {206wrapper.close && wrapper.close.call(this, initData);207}208errorThrown = false;209} finally {210if (errorThrown) {211// The closer for wrapper i threw an error; close the remaining212// wrappers but silence any exceptions from them to ensure that the213// first error is the one to bubble up.214try {215this.closeAll(i + 1);216} catch (e) {217}218}219}220}221this.wrapperInitData.length = 0;222}223};224225var Transaction = {226227Mixin: Mixin,228229/**230* Token to look for to determine if an error occured.231*/232OBSERVED_ERROR: {}233234};235236module.exports = Transaction;237238239