Frank Shearar uploaded a new version of Kernel to project The Trunk:
http://source.squeak.org/trunk/Kernel-fbs.762.mcz ==================== Summary ==================== Name: Kernel-fbs.762 Author: fbs Time: 30 May 2013, 11:07:50.674 pm UUID: 189ac08f-d45b-40f8-b7cd-e183ae46f3bf Ancestors: Kernel-fbs.761 Turn Promise into a fully chainable object with decent error handling. (This implements the Smalltalk equivalent of JavaScript's Promises/A+ specification.) =============== Diff against Kernel-fbs.761 =============== Item was changed: Object subclass: #Promise + instanceVariableNames: 'onError value resolvers mutex state error rejectors rejecters' - instanceVariableNames: 'isResolved value resolvers mutex' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Processes'! + !Promise commentStamp: 'fbs 5/17/2013 18:23' prior: 0! - !Promise commentStamp: 'jcg 12/17/2009 02:22' prior: 0! I represent the result of an asynchronous message. Once the message is processed, I will be resolved to a value. I am typically instantiated by invocations of #futureSend:at:args: (and not by #futureDo:atArgs:). + See class-comment of FutureNode. + + I also implement the Promises/A+ Javascript specification. This allows you to chain my instances to perform arbitrarily complex asynchronous tasks with error handling baked in. + + A Promise may be in one of three possible states: #pending, #fulfilled or #rejected. A Promise may move from #pending -> #fulfilled, or from #pending -> #rejected. No other state changes may occur. Once #fulfilled or #rejected, a Promise's value must change.! - See class-comment of FutureNode.! Item was added: + ----- Method: Promise class>>ifRejected: (in category 'instance creation') ----- + ifRejected: aBlock + ^ Promise basicNew initializeWithIfRejected: aBlock.! Item was added: + ----- Method: Promise class>>unit: (in category 'instance creation') ----- + unit: anObject + "Return a resolved Promise. #new is the other half of Promise's unit function; #new returns an unresolved Promise." + ^ Promise basicNew initializeWithResolvedValue: anObject.! Item was added: + ----- Method: Promise>>error (in category 'accessing') ----- + error + ^ error.! Item was added: + ----- Method: Promise>>evaluateRejecter: (in category 'private') ----- + evaluateRejecter: rejecterBlock + ^ rejecterBlock cull: error.! Item was changed: ----- Method: Promise>>evaluateResolver: (in category 'private') ----- evaluateResolver: resolverBlock + ^ resolverBlock cull: value.! - resolverBlock cull: value.! Item was added: + ----- Method: Promise>>ifRejected: (in category 'monad') ----- + ifRejected: errBlock + ^ self then: [:ignored | "Do nothing"] ifRejected: errBlock.! Item was changed: ----- Method: Promise>>initialize (in category 'initialize') ----- initialize + state := #pending. - isResolved := false. resolvers := #(). + rejecters := #(). mutex := Mutex new.! Item was added: + ----- Method: Promise>>initializeWithIfRejected: (in category 'initialize') ----- + initializeWithIfRejected: aBlock + self initialize. + rejecters := {aBlock}.! Item was added: + ----- Method: Promise>>initializeWithResolvedValue: (in category 'initialize') ----- + initializeWithResolvedValue: anObject + self initialize. + self resolveWith: anObject.! Item was added: + ----- Method: Promise>>isPromise (in category 'testing') ----- + isPromise + ^ true.! Item was added: + ----- Method: Promise>>isRejected (in category 'testing') ----- + isRejected + ^ state == #rejected.! Item was changed: ----- Method: Promise>>isResolved (in category 'testing') ----- isResolved + ^ state == #fulfilled.! - ^isResolved! Item was added: + ----- Method: Promise>>printOn: (in category 'printing') ----- + printOn: aStream + aStream nextPutAll: 'a Promise'. + self isResolved ifTrue: [ + aStream + nextPutAll: '(resolved: '; + nextPutAll: value printString; + nextPutAll: ')']. + self isRejected ifTrue: [ + aStream + nextPutAll: '(rejected: '; + nextPutAll: error printString; + nextPutAll: ')'].! Item was added: + ----- Method: Promise>>rejectWith: (in category 'resolving') ----- + rejectWith: anObject + "Reject this promise." + mutex critical: [ + (state == #fulfilled) ifTrue: [self error: 'Promise was already resolved']. + (state == #rejected) ifTrue: [self error: 'Promise was already rejected']. + error := anObject. + state := #rejected. + rejecters do: [:r | self evaluateRejecter: r]].! Item was changed: ----- Method: Promise>>resolveWith: (in category 'resolving') ----- resolveWith: arg "Resolve this promise" mutex critical: [ + (state == #fulfilled) ifTrue: [self error: 'Promise was already resolved']. + (state == #rejected) ifTrue: [self error: 'Promise was already resolved']. - isResolved ifTrue: [self error: 'Promise was already resolved']. value := arg. + state := #fulfilled. + resolvers do: [:r | + self evaluateResolver: r]].! - isResolved := true. - resolvers do: [:r | self evaluateResolver: r]. - ].! Item was added: + ----- Method: Promise>>then: (in category 'monad') ----- + then: resolvedBlock + ^ self then: resolvedBlock ifRejected: [:ignored | "Do nothing"].! Item was added: + ----- Method: Promise>>then:ifRejected: (in category 'monad') ----- + then: resolvedBlock ifRejected: errBlock + "Return a Promise that, if it resolves, runs the resolvedBlock. If resolution throws an Exception, it runs the errBlock." + | p | + p := Promise new. + self whenResolved: [:v | + [p resolveWith: (resolvedBlock value: v)] + on: Error do: [:e | p rejectWith: e]]. + self whenRejected: [:e | p rejectWith: (errBlock value: e)]. + ^ p.! Item was changed: ----- Method: Promise>>waitTimeoutMSecs: (in category 'waiting') ----- waitTimeoutMSecs: msecs "Wait for at most the given number of milliseconds for this promise to resolve. Answer true if it is resolved, false otherwise." | sema delay | sema := Semaphore new. self whenResolved: [sema signal]. delay := Delay timeoutSemaphore: sema afterMSecs: msecs. [sema wait] ensure: [delay unschedule]. + ^ self isResolved.! - ^isResolved! Item was added: + ----- Method: Promise>>whenRejected: (in category 'resolving') ----- + whenRejected: aBlock + "Evaluate aBlock when I am rejected" + aBlock numArgs <= 1 ifFalse: [self error: 'Must be 0- or 1-argument block']. + ^ mutex critical: [ + rejecters := rejecters copyWith: aBlock. + self isRejected ifTrue:[self evaluateRejecter: aBlock]. + ]! Item was changed: ----- Method: Promise>>whenResolved: (in category 'resolving') ----- whenResolved: aBlock "Evaluate aBlock when I am resolved" aBlock numArgs <= 1 ifFalse:[self error: 'Must be 0- or 1-argument block']. + ^ mutex critical: [ - mutex critical: [ resolvers := resolvers copyWith: aBlock. self isResolved ifTrue:[self evaluateResolver: aBlock]. ]! |
Free forum by Nabble | Edit this page |