[squeak-dev] The Trunk: Kernel-fbs.762.mcz

commits at source.squeak.org commits at source.squeak.org
Thu May 30 22:09:18 UTC 2013


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].
  	]!



More information about the Squeak-dev mailing list