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() ? null
Checkout 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.promise
Called 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 @deferred
In 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 @deferred
Progres 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))
return
Handle 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