The Trunk: Kernel-fbs.762.mcz

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

The Trunk: Kernel-fbs.762.mcz

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