Promise = require 'bluebird'
__requestNumber = 0
class Request A Request transaction requensts and provides a static interface for responses.
To make a request, call Request.ask() Ask takes an optional hash;
if you want to request a transaction on a particular database adapter, call
`Request.ask({adapter: adapter}).
The transaction class itself, will call fulfillTransaction, and pass in another hash. If objects under matching keys are identical, the transaction request is fulfilled.
The base implementation supports the adapter keyword, but
other implementers could override this.
When a transaction request receives no immediate response, the global handler is called. By default, it passes back “null”, but the base transaction implementation overrides this to wait further if there are any outstanding transactions.
Promise = require 'bluebird'
__requestNumber = 0
class Request Wrapper around new Request, then getTransaction
@ask: (id, name)->
(new Request id, name).getTransaction()Get a shared client in a transaction; returns null if no transaction.
@client: (id, name)->
return Request.ask(id, name).then (transaction)->
transaction?.client() ? nullCheckout client from transaction.
@takeClient: (id, name)->
return Request.ask(id, name.then) (transaction)->
transaction?.takeClient() ? null
constructor: (@id, @name)->
__requestNumber += 1
@name = "?#{__requestNumber}" if !@name?Returns a promise with the transaction.
Sends self as progress; transaction will call fulfill where the
a progress handler handles and resolves the promise with the transaction.
The call to fulfill takes place after getTransaction, but is
still synchronous. So if we delay one tick we should be assured of
receiving transaction if it exists. Thus, if we wait one tick more
and still haven’t got a transaction, we assume there is no wrapper.
getTransaction: ->
self = this
@deferred = d = Promise.defer()
Request.logger.debug("ASK #{@name}")
process.nextTick ->
Request.logger.debug("(ASK UP: #{self.name.slice(0, 4)})")
d.progress self
process.nextTick ->
if d.promise.isPending()
Request.logger.debug("(ASK UNA: #{self.name.slice(0, 4)})")
Request.handleUnanswered(self)create error so we can capture the “getTransaction” stack trace in case handler causes us to reject.
@callerStack = new Error().stack
return d.promiseCalled by the transaction progress handler to pass back the transaction.
fulfill: (transaction)->
if @deferred?.promise.isPending()
Request.logger.debug(
"FULFILL #{@name} by:", transaction?.name.slice(0,4), '---')
@deferred.resolve(transaction)
delete @deferredIn case a progress handler wants to abort (e.g. deadlock stuck transaction timeout). Currently not used, but could be useful as the transaction manager might be able to detect some resource contention that the database might not be aware of.
reject: (reason)->
if @deferred?.promise.isPending()
@deferred.reject(reason)
delete @deferredProgres handler used by transaction to listen to and
fulfill requests. transaction should be the current transaction.
promise should be the promise we are wrapping in which
to fulfill transaction requests. id, if passed,
will be used to match requests.
NB “progressed” seems not to chain exception stacks correctly.
@handle: (transaction, promise, id)->
Request.logger.debug("HANDLE BY", transaction?.name.slice(0,4) ? '----')
if !promise? or !promise.progressed?
throw new Error("Cannot pass transaction: no promise; got #{promise}")
return promise.progressed (request, stack)->unwrap — annoying oddity
while request.value?
request = request.value
if !(request instanceof Request)
return
if !id? or !request.id? or request.id == id
request.fulfill(transaction)
throw {name:'StopProgressPropagation'}
Request.logger.debug(
"REQ-#{request.name} doesn't match", transaction.name.slice(0,4))
returnHandle unanswered by passing a null transaction. This could be a useful default, but currently is overridden by the transaction manager.
@handleUnanswered = (request)->
debugger
request.fulfill(null)
module.exports = Request