Browse Source

Move Thenable class to TThenable trait.

Herbert Vojčík 7 years ago
parent
commit
61f19cfe38
4 changed files with 210 additions and 217 deletions
  1. 5 0
      API-CHANGES.txt
  2. 1 0
      CHANGELOG
  3. 151 157
      src/Kernel-Promises.js
  4. 53 60
      src/Kernel-Promises.st

+ 5 - 0
API-CHANGES.txt

@@ -1,3 +1,8 @@
+0.19.0:
+
+* Remove class Thenable, add trait TThenable.
+
+
 0.18.3:
 
 * Move {add,remove}Element from Array.prototype to kernel API.

+ 1 - 0
CHANGELOG

@@ -2,6 +2,7 @@
 ===================================
 
 * Removed BehaviorBody in favour of traits TBehaviorDefaults and TBehaviorProvider.
+* Move Thenable class to TThenable trait.
 
 Commits: https://lolg.it/amber/amber/commits/0.19.0.
 

+ 151 - 157
src/Kernel-Promises.js

@@ -6,371 +6,365 @@ $core.addPackage("Kernel-Promises");
 $core.packages["Kernel-Promises"].innerEval = function (expr) { return eval(expr); };
 $core.packages["Kernel-Promises"].transport = {"type":"amd","amdNamespace":"amber_core"};
 
-$core.addClass("Thenable", $globals.Object, [], "Kernel-Promises");
-//>>excludeStart("ide", pragmas.excludeIdeData);
-$globals.Thenable.comment="I am the abstract base class for Promises.\x0a\x0aMy subclasses should wrap existing JS implementations.\x0a\x0aI contain methods that wrap Promises/A+ `.then` behaviour.";
-//>>excludeEnd("ide");
+$core.addClass("Promise", $globals.Object, [], "Kernel-Promises");
+
 $core.addMethod(
 $core.method({
-selector: "catch:",
-protocol: "promises",
-fn: function (aBlock){
+selector: "all:",
+protocol: "composites",
+fn: function (aCollection){
 var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return self.then(null, function (err) {return $core.seamless(function () {
-    return aBlock._value_(err);
-})});
+return Promise.all($recv(aCollection)._asArray());
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"catch:",{aBlock:aBlock},$globals.Thenable)});
+}, function($ctx1) {$ctx1.fill(self,"all:",{aCollection:aCollection},$globals.Promise.klass)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
-args: ["aBlock"],
-source: "catch: aBlock\x0a<inlineJS: 'return self.then(null, function (err) {return $core.seamless(function () {\x0a    return aBlock._value_(err);\x0a})})'>",
+args: ["aCollection"],
+source: "all: aCollection\x0a\x22Returns a Promise resolved with results of sub-promises.\x22\x0a<inlineJS: 'return Promise.all($recv(aCollection)._asArray())'>",
 referencedClasses: [],
 //>>excludeEnd("ide");
 messageSends: []
 }),
-$globals.Thenable);
+$globals.Promise.klass);
 
 $core.addMethod(
 $core.method({
-selector: "on:do:",
-protocol: "promises",
-fn: function (aClass,aBlock){
+selector: "any:",
+protocol: "composites",
+fn: function (aCollection){
 var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return self.then(null, function (err) {return $core.seamless(function () {
-    if (err._isKindOf_(aClass)) return aBlock._value_(err);
-    else throw err;
-})});
+return Promise.race($recv(aCollection)._asArray());
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"on:do:",{aClass:aClass,aBlock:aBlock},$globals.Thenable)});
+}, function($ctx1) {$ctx1.fill(self,"any:",{aCollection:aCollection},$globals.Promise.klass)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
-args: ["aClass", "aBlock"],
-source: "on: aClass do: aBlock\x0a<inlineJS: 'return self.then(null, function (err) {return $core.seamless(function () {\x0a    if (err._isKindOf_(aClass)) return aBlock._value_(err);\x0a    else throw err;\x0a})})'>",
+args: ["aCollection"],
+source: "any: aCollection\x0a\x22Returns a Promise resolved with first result of sub-promises.\x22\x0a<inlineJS: 'return Promise.race($recv(aCollection)._asArray())'>",
 referencedClasses: [],
 //>>excludeEnd("ide");
 messageSends: []
 }),
-$globals.Thenable);
+$globals.Promise.klass);
 
 $core.addMethod(
 $core.method({
-selector: "on:do:catch:",
-protocol: "promises",
-fn: function (aClass,aBlock,anotherBlock){
+selector: "forBlock:",
+protocol: "instance creation",
+fn: function (aBlock){
 var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return self.then(null, function (err) {return $core.seamless(function () {
-    try { if (err._isKindOf_(aClass)) return aBlock._value_(err); } catch (e) { err = e; }
-    return anotherBlock._value_(err);
-})});
-return self;
+return $recv(self._new())._then_(aBlock);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"on:do:catch:",{aClass:aClass,aBlock:aBlock,anotherBlock:anotherBlock},$globals.Thenable)});
+}, function($ctx1) {$ctx1.fill(self,"forBlock:",{aBlock:aBlock},$globals.Promise.klass)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
-args: ["aClass", "aBlock", "anotherBlock"],
-source: "on: aClass do: aBlock catch: anotherBlock\x0a<inlineJS: 'return self.then(null, function (err) {return $core.seamless(function () {\x0a    try { if (err._isKindOf_(aClass)) return aBlock._value_(err); } catch (e) { err = e; }\x0a    return anotherBlock._value_(err);\x0a})})'>",
+args: ["aBlock"],
+source: "forBlock: aBlock\x0a\x22Returns a Promise that is resolved with the value of aBlock,\x0aand rejected if error happens while evaluating aBlock.\x22\x0a\x09^ self new then: aBlock",
 referencedClasses: [],
 //>>excludeEnd("ide");
-messageSends: []
+messageSends: ["then:", "new"]
 }),
-$globals.Thenable);
+$globals.Promise.klass);
 
 $core.addMethod(
 $core.method({
-selector: "then:",
-protocol: "promises",
-fn: function (aBlockOrArray){
+selector: "new",
+protocol: "instance creation",
+fn: function (){
 var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-
-var array = Array.isArray(aBlockOrArray) ? aBlockOrArray : [aBlockOrArray];
-return array.reduce(function (soFar, aBlock) {
-    return soFar.then(typeof aBlock === "function" && aBlock.length > 1 ?
-        function (result) {return $core.seamless(function () {
-            if (Array.isArray(result)) {
-                return aBlock._valueWithPossibleArguments_([result].concat(result.slice(0, aBlock.length-1)));
-            } else {
-                return aBlock._value_(result);
-            }
-        })} :
-        function (result) {return $core.seamless(function () {
-            return aBlock._value_(result);
-        })}
-    );
-}, self);
+return Promise.resolve();
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"then:",{aBlockOrArray:aBlockOrArray},$globals.Thenable)});
+}, function($ctx1) {$ctx1.fill(self,"new",{},$globals.Promise.klass)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
-args: ["aBlockOrArray"],
-source: "then: aBlockOrArray\x0a\x22Accepts a block or array of blocks.\x0aEach of blocks in the array or the singleton one is\x0aused in .then call to a promise, to accept a result\x0aand transform it to the result for the next one.\x0aIn case a block has more than one argument\x0aand result is an array, first n-1 elements of the array\x0aare put into additional arguments beyond the first.\x0aThe first argument always contains the result as-is.\x22\x0a<inlineJS: '\x0avar array = Array.isArray(aBlockOrArray) ? aBlockOrArray : [aBlockOrArray];\x0areturn array.reduce(function (soFar, aBlock) {\x0a    return soFar.then(typeof aBlock === \x22function\x22 && aBlock.length > 1 ?\x0a        function (result) {return $core.seamless(function () {\x0a            if (Array.isArray(result)) {\x0a                return aBlock._valueWithPossibleArguments_([result].concat(result.slice(0, aBlock.length-1)));\x0a            } else {\x0a                return aBlock._value_(result);\x0a            }\x0a        })} :\x0a        function (result) {return $core.seamless(function () {\x0a            return aBlock._value_(result);\x0a        })}\x0a    );\x0a}, self)'>",
+args: [],
+source: "new\x0a\x22Returns a dumb Promise resolved with nil.\x22\x0a<inlineJS: 'return Promise.resolve()'>",
 referencedClasses: [],
 //>>excludeEnd("ide");
 messageSends: []
 }),
-$globals.Thenable);
+$globals.Promise.klass);
 
 $core.addMethod(
 $core.method({
-selector: "then:catch:",
-protocol: "promises",
-fn: function (aBlockOrArray,anotherBlock){
+selector: "new:",
+protocol: "instance creation",
+fn: function (aBlock){
 var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return $recv(self._then_(aBlockOrArray))._catch_(anotherBlock);
+return new Promise(function (resolve, reject) {
+    var model = {value: resolve, signal: reject}; // TODO make faster
+    aBlock._value_(model);
+});
+return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"then:catch:",{aBlockOrArray:aBlockOrArray,anotherBlock:anotherBlock},$globals.Thenable)});
+}, function($ctx1) {$ctx1.fill(self,"new:",{aBlock:aBlock},$globals.Promise.klass)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
-args: ["aBlockOrArray", "anotherBlock"],
-source: "then: aBlockOrArray catch: anotherBlock\x0a\x09^ (self then: aBlockOrArray) catch: anotherBlock",
+args: ["aBlock"],
+source: "new: aBlock\x0a\x22Returns a Promise that is eventually resolved or rejected.\x0aPass a block that is called with one argument, model.\x0aYou should call model value: ... to resolve the promise\x0aand model signal: ... to reject the promise.\x0aIf error happens during run of the block,\x0apromise is rejected with that error as well.\x22\x0a<inlineJS: 'return new Promise(function (resolve, reject) {\x0a    var model = {value: resolve, signal: reject}; // TODO make faster\x0a    aBlock._value_(model);\x0a})'>",
 referencedClasses: [],
 //>>excludeEnd("ide");
-messageSends: ["catch:", "then:"]
+messageSends: []
 }),
-$globals.Thenable);
+$globals.Promise.klass);
 
 $core.addMethod(
 $core.method({
-selector: "then:on:do:",
-protocol: "promises",
-fn: function (aBlockOrArray,aClass,aBlock){
+selector: "signal:",
+protocol: "instance creation",
+fn: function (anObject){
 var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return $recv(self._then_(aBlockOrArray))._on_do_(aClass,aBlock);
+return $recv(anObject)._in_(function (x) {return Promise.reject(x)});
+return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"then:on:do:",{aBlockOrArray:aBlockOrArray,aClass:aClass,aBlock:aBlock},$globals.Thenable)});
+}, function($ctx1) {$ctx1.fill(self,"signal:",{anObject:anObject},$globals.Promise.klass)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
-args: ["aBlockOrArray", "aClass", "aBlock"],
-source: "then: aBlockOrArray on: aClass do: aBlock\x0a\x09^ (self then: aBlockOrArray) on: aClass do: aBlock",
+args: ["anObject"],
+source: "signal: anObject\x0a\x22Returns a Promise rejected with anObject.\x22\x0a<inlineJS: 'return $recv(anObject)._in_(function (x) {return Promise.reject(x)})'>",
 referencedClasses: [],
 //>>excludeEnd("ide");
-messageSends: ["on:do:", "then:"]
+messageSends: []
 }),
-$globals.Thenable);
+$globals.Promise.klass);
 
 $core.addMethod(
 $core.method({
-selector: "then:on:do:catch:",
-protocol: "promises",
-fn: function (aBlockOrArray,aClass,aBlock,anotherBlock){
+selector: "value:",
+protocol: "instance creation",
+fn: function (anObject){
 var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return $recv($recv(self._then_(aBlockOrArray))._on_do_(aClass,aBlock))._catch_(anotherBlock);
+return $recv(anObject)._in_(function (x) {return Promise.resolve(x)});
+return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"then:on:do:catch:",{aBlockOrArray:aBlockOrArray,aClass:aClass,aBlock:aBlock,anotherBlock:anotherBlock},$globals.Thenable)});
+}, function($ctx1) {$ctx1.fill(self,"value:",{anObject:anObject},$globals.Promise.klass)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
-args: ["aBlockOrArray", "aClass", "aBlock", "anotherBlock"],
-source: "then: aBlockOrArray on: aClass do: aBlock catch: anotherBlock\x0a\x09^ ((self then: aBlockOrArray) on: aClass do: aBlock) catch: anotherBlock",
+args: ["anObject"],
+source: "value: anObject\x0a\x22Returns a Promise resolved with anObject.\x22\x0a<inlineJS: 'return $recv(anObject)._in_(function (x) {return Promise.resolve(x)})'>",
 referencedClasses: [],
 //>>excludeEnd("ide");
-messageSends: ["catch:", "on:do:", "then:"]
+messageSends: []
 }),
-$globals.Thenable);
-
-
+$globals.Promise.klass);
 
-$core.addClass("Promise", $globals.Thenable, [], "Kernel-Promises");
 
+$core.addTrait("TThenable", "Kernel-Promises");
 $core.addMethod(
 $core.method({
-selector: "all:",
-protocol: "composites",
-fn: function (aCollection){
+selector: "catch:",
+protocol: "promises",
+fn: function (aBlock){
 var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return Promise.all($recv(aCollection)._asArray());
+return self.then(null, function (err) {return $core.seamless(function () {
+    return aBlock._value_(err);
+})});
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"all:",{aCollection:aCollection},$globals.Promise.klass)});
+}, function($ctx1) {$ctx1.fill(self,"catch:",{aBlock:aBlock},$globals.TThenable)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
-args: ["aCollection"],
-source: "all: aCollection\x0a\x22Returns a Promise resolved with results of sub-promises.\x22\x0a<inlineJS: 'return Promise.all($recv(aCollection)._asArray())'>",
+args: ["aBlock"],
+source: "catch: aBlock\x0a<inlineJS: 'return self.then(null, function (err) {return $core.seamless(function () {\x0a    return aBlock._value_(err);\x0a})})'>",
 referencedClasses: [],
 //>>excludeEnd("ide");
 messageSends: []
 }),
-$globals.Promise.klass);
+$globals.TThenable);
 
 $core.addMethod(
 $core.method({
-selector: "any:",
-protocol: "composites",
-fn: function (aCollection){
+selector: "on:do:",
+protocol: "promises",
+fn: function (aClass,aBlock){
 var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return Promise.race($recv(aCollection)._asArray());
+return self.then(null, function (err) {return $core.seamless(function () {
+    if (err._isKindOf_(aClass)) return aBlock._value_(err);
+    else throw err;
+})});
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"any:",{aCollection:aCollection},$globals.Promise.klass)});
+}, function($ctx1) {$ctx1.fill(self,"on:do:",{aClass:aClass,aBlock:aBlock},$globals.TThenable)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
-args: ["aCollection"],
-source: "any: aCollection\x0a\x22Returns a Promise resolved with first result of sub-promises.\x22\x0a<inlineJS: 'return Promise.race($recv(aCollection)._asArray())'>",
+args: ["aClass", "aBlock"],
+source: "on: aClass do: aBlock\x0a<inlineJS: 'return self.then(null, function (err) {return $core.seamless(function () {\x0a    if (err._isKindOf_(aClass)) return aBlock._value_(err);\x0a    else throw err;\x0a})})'>",
 referencedClasses: [],
 //>>excludeEnd("ide");
 messageSends: []
 }),
-$globals.Promise.klass);
+$globals.TThenable);
 
 $core.addMethod(
 $core.method({
-selector: "forBlock:",
-protocol: "instance creation",
-fn: function (aBlock){
+selector: "on:do:catch:",
+protocol: "promises",
+fn: function (aClass,aBlock,anotherBlock){
 var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return $recv(self._new())._then_(aBlock);
+return $recv(self._on_do_(aClass,aBlock))._catch_(anotherBlock);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"forBlock:",{aBlock:aBlock},$globals.Promise.klass)});
+}, function($ctx1) {$ctx1.fill(self,"on:do:catch:",{aClass:aClass,aBlock:aBlock,anotherBlock:anotherBlock},$globals.TThenable)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
-args: ["aBlock"],
-source: "forBlock: aBlock\x0a\x22Returns a Promise that is resolved with the value of aBlock,\x0aand rejected if error happens while evaluating aBlock.\x22\x0a\x09^ self new then: aBlock",
+args: ["aClass", "aBlock", "anotherBlock"],
+source: "on: aClass do: aBlock catch: anotherBlock\x0a\x09^ (self on: aClass do: aBlock) catch: anotherBlock",
 referencedClasses: [],
 //>>excludeEnd("ide");
-messageSends: ["then:", "new"]
+messageSends: ["catch:", "on:do:"]
 }),
-$globals.Promise.klass);
+$globals.TThenable);
 
 $core.addMethod(
 $core.method({
-selector: "new",
-protocol: "instance creation",
-fn: function (){
+selector: "then:",
+protocol: "promises",
+fn: function (aBlockOrArray){
 var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return Promise.resolve();
+
+var array = Array.isArray(aBlockOrArray) ? aBlockOrArray : [aBlockOrArray];
+return array.reduce(function (soFar, aBlock) {
+    return soFar.then(typeof aBlock === "function" && aBlock.length > 1 ?
+        function (result) {return $core.seamless(function () {
+            if (Array.isArray(result)) {
+                return aBlock._valueWithPossibleArguments_([result].concat(result.slice(0, aBlock.length-1)));
+            } else {
+                return aBlock._value_(result);
+            }
+        })} :
+        function (result) {return $core.seamless(function () {
+            return aBlock._value_(result);
+        })}
+    );
+}, self);
 return self;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"new",{},$globals.Promise.klass)});
+}, function($ctx1) {$ctx1.fill(self,"then:",{aBlockOrArray:aBlockOrArray},$globals.TThenable)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
-args: [],
-source: "new\x0a\x22Returns a dumb Promise resolved with nil.\x22\x0a<inlineJS: 'return Promise.resolve()'>",
+args: ["aBlockOrArray"],
+source: "then: aBlockOrArray\x0a\x22Accepts a block or array of blocks.\x0aEach of blocks in the array or the singleton one is\x0aused in .then call to a promise, to accept a result\x0aand transform it to the result for the next one.\x0aIn case a block has more than one argument\x0aand result is an array, first n-1 elements of the array\x0aare put into additional arguments beyond the first.\x0aThe first argument always contains the result as-is.\x22\x0a<inlineJS: '\x0avar array = Array.isArray(aBlockOrArray) ? aBlockOrArray : [aBlockOrArray];\x0areturn array.reduce(function (soFar, aBlock) {\x0a    return soFar.then(typeof aBlock === \x22function\x22 && aBlock.length > 1 ?\x0a        function (result) {return $core.seamless(function () {\x0a            if (Array.isArray(result)) {\x0a                return aBlock._valueWithPossibleArguments_([result].concat(result.slice(0, aBlock.length-1)));\x0a            } else {\x0a                return aBlock._value_(result);\x0a            }\x0a        })} :\x0a        function (result) {return $core.seamless(function () {\x0a            return aBlock._value_(result);\x0a        })}\x0a    );\x0a}, self)'>",
 referencedClasses: [],
 //>>excludeEnd("ide");
 messageSends: []
 }),
-$globals.Promise.klass);
+$globals.TThenable);
 
 $core.addMethod(
 $core.method({
-selector: "new:",
-protocol: "instance creation",
-fn: function (aBlock){
+selector: "then:catch:",
+protocol: "promises",
+fn: function (aBlockOrArray,anotherBlock){
 var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return new Promise(function (resolve, reject) {
-    var model = {value: resolve, signal: reject}; // TODO make faster
-    aBlock._value_(model);
-});
-return self;
+return $recv(self._then_(aBlockOrArray))._catch_(anotherBlock);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"new:",{aBlock:aBlock},$globals.Promise.klass)});
+}, function($ctx1) {$ctx1.fill(self,"then:catch:",{aBlockOrArray:aBlockOrArray,anotherBlock:anotherBlock},$globals.TThenable)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
-args: ["aBlock"],
-source: "new: aBlock\x0a\x22Returns a Promise that is eventually resolved or rejected.\x0aPass a block that is called with one argument, model.\x0aYou should call model value: ... to resolve the promise\x0aand model signal: ... to reject the promise.\x0aIf error happens during run of the block,\x0apromise is rejected with that error as well.\x22\x0a<inlineJS: 'return new Promise(function (resolve, reject) {\x0a    var model = {value: resolve, signal: reject}; // TODO make faster\x0a    aBlock._value_(model);\x0a})'>",
+args: ["aBlockOrArray", "anotherBlock"],
+source: "then: aBlockOrArray catch: anotherBlock\x0a\x09^ (self then: aBlockOrArray) catch: anotherBlock",
 referencedClasses: [],
 //>>excludeEnd("ide");
-messageSends: []
+messageSends: ["catch:", "then:"]
 }),
-$globals.Promise.klass);
+$globals.TThenable);
 
 $core.addMethod(
 $core.method({
-selector: "signal:",
-protocol: "instance creation",
-fn: function (anObject){
+selector: "then:on:do:",
+protocol: "promises",
+fn: function (aBlockOrArray,aClass,aBlock){
 var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return $recv(anObject)._in_(function (x) {return Promise.reject(x)});
-return self;
+return $recv(self._then_(aBlockOrArray))._on_do_(aClass,aBlock);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"signal:",{anObject:anObject},$globals.Promise.klass)});
+}, function($ctx1) {$ctx1.fill(self,"then:on:do:",{aBlockOrArray:aBlockOrArray,aClass:aClass,aBlock:aBlock},$globals.TThenable)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
-args: ["anObject"],
-source: "signal: anObject\x0a\x22Returns a Promise rejected with anObject.\x22\x0a<inlineJS: 'return $recv(anObject)._in_(function (x) {return Promise.reject(x)})'>",
+args: ["aBlockOrArray", "aClass", "aBlock"],
+source: "then: aBlockOrArray on: aClass do: aBlock\x0a\x09^ (self then: aBlockOrArray) on: aClass do: aBlock",
 referencedClasses: [],
 //>>excludeEnd("ide");
-messageSends: []
+messageSends: ["on:do:", "then:"]
 }),
-$globals.Promise.klass);
+$globals.TThenable);
 
 $core.addMethod(
 $core.method({
-selector: "value:",
-protocol: "instance creation",
-fn: function (anObject){
+selector: "then:on:do:catch:",
+protocol: "promises",
+fn: function (aBlockOrArray,aClass,aBlock,anotherBlock){
 var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return $recv(anObject)._in_(function (x) {return Promise.resolve(x)});
-return self;
+return $recv($recv(self._then_(aBlockOrArray))._on_do_(aClass,aBlock))._catch_(anotherBlock);
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
-}, function($ctx1) {$ctx1.fill(self,"value:",{anObject:anObject},$globals.Promise.klass)});
+}, function($ctx1) {$ctx1.fill(self,"then:on:do:catch:",{aBlockOrArray:aBlockOrArray,aClass:aClass,aBlock:aBlock,anotherBlock:anotherBlock},$globals.TThenable)});
 //>>excludeEnd("ctx");
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
-args: ["anObject"],
-source: "value: anObject\x0a\x22Returns a Promise resolved with anObject.\x22\x0a<inlineJS: 'return $recv(anObject)._in_(function (x) {return Promise.resolve(x)})'>",
+args: ["aBlockOrArray", "aClass", "aBlock", "anotherBlock"],
+source: "then: aBlockOrArray on: aClass do: aBlock catch: anotherBlock\x0a\x09^ ((self then: aBlockOrArray) on: aClass do: aBlock) catch: anotherBlock",
 referencedClasses: [],
 //>>excludeEnd("ide");
-messageSends: []
+messageSends: ["catch:", "on:do:", "then:"]
 }),
-$globals.Promise.klass);
+$globals.TThenable);
+
+$core.setTraitComposition([{trait: $globals.TThenable}], $globals.Promise);
 
 $core.addMethod(
 $core.method({

+ 53 - 60
src/Kernel-Promises.st

@@ -1,15 +1,60 @@
 Smalltalk createPackage: 'Kernel-Promises'!
-Object subclass: #Thenable
+Object subclass: #Promise
 	instanceVariableNames: ''
 	package: 'Kernel-Promises'!
-!Thenable commentStamp!
-I am the abstract base class for Promises.
 
-My subclasses should wrap existing JS implementations.
+!Promise class methodsFor: 'composites'!
+
+all: aCollection
+"Returns a Promise resolved with results of sub-promises."
+<inlineJS: 'return Promise.all($recv(aCollection)._asArray())'>
+!
+
+any: aCollection
+"Returns a Promise resolved with first result of sub-promises."
+<inlineJS: 'return Promise.race($recv(aCollection)._asArray())'>
+! !
+
+!Promise class methodsFor: 'instance creation'!
+
+forBlock: aBlock
+"Returns a Promise that is resolved with the value of aBlock,
+and rejected if error happens while evaluating aBlock."
+	^ self new then: aBlock
+!
+
+new
+"Returns a dumb Promise resolved with nil."
+<inlineJS: 'return Promise.resolve()'>
+!
+
+new: aBlock
+"Returns a Promise that is eventually resolved or rejected.
+Pass a block that is called with one argument, model.
+You should call model value: ... to resolve the promise
+and model signal: ... to reject the promise.
+If error happens during run of the block,
+promise is rejected with that error as well."
+<inlineJS: 'return new Promise(function (resolve, reject) {
+    var model = {value: resolve, signal: reject}; // TODO make faster
+    aBlock._value_(model);
+})'>
+!
 
-I contain methods that wrap Promises/A+ `.then` behaviour.!
+signal: anObject
+"Returns a Promise rejected with anObject."
+<inlineJS: 'return $recv(anObject)._in_(function (x) {return Promise.reject(x)})'>
+!
+
+value: anObject
+"Returns a Promise resolved with anObject."
+<inlineJS: 'return $recv(anObject)._in_(function (x) {return Promise.resolve(x)})'>
+! !
+
+Trait named: #TThenable
+	package: 'Kernel-Promises'!
 
-!Thenable methodsFor: 'promises'!
+!TThenable methodsFor: 'promises'!
 
 catch: aBlock
 <inlineJS: 'return self.then(null, function (err) {return $core.seamless(function () {
@@ -25,10 +70,7 @@ on: aClass do: aBlock
 !
 
 on: aClass do: aBlock catch: anotherBlock
-<inlineJS: 'return self.then(null, function (err) {return $core.seamless(function () {
-    try { if (err._isKindOf_(aClass)) return aBlock._value_(err); } catch (e) { err = e; }
-    return anotherBlock._value_(err);
-})})'>
+	^ (self on: aClass do: aBlock) catch: anotherBlock
 !
 
 then: aBlockOrArray
@@ -70,56 +112,7 @@ then: aBlockOrArray on: aClass do: aBlock catch: anotherBlock
 	^ ((self then: aBlockOrArray) on: aClass do: aBlock) catch: anotherBlock
 ! !
 
-Thenable subclass: #Promise
-	instanceVariableNames: ''
-	package: 'Kernel-Promises'!
-
-!Promise class methodsFor: 'composites'!
-
-all: aCollection
-"Returns a Promise resolved with results of sub-promises."
-<inlineJS: 'return Promise.all($recv(aCollection)._asArray())'>
-!
-
-any: aCollection
-"Returns a Promise resolved with first result of sub-promises."
-<inlineJS: 'return Promise.race($recv(aCollection)._asArray())'>
-! !
-
-!Promise class methodsFor: 'instance creation'!
-
-forBlock: aBlock
-"Returns a Promise that is resolved with the value of aBlock,
-and rejected if error happens while evaluating aBlock."
-	^ self new then: aBlock
-!
-
-new
-"Returns a dumb Promise resolved with nil."
-<inlineJS: 'return Promise.resolve()'>
-!
-
-new: aBlock
-"Returns a Promise that is eventually resolved or rejected.
-Pass a block that is called with one argument, model.
-You should call model value: ... to resolve the promise
-and model signal: ... to reject the promise.
-If error happens during run of the block,
-promise is rejected with that error as well."
-<inlineJS: 'return new Promise(function (resolve, reject) {
-    var model = {value: resolve, signal: reject}; // TODO make faster
-    aBlock._value_(model);
-})'>
-!
-
-signal: anObject
-"Returns a Promise rejected with anObject."
-<inlineJS: 'return $recv(anObject)._in_(function (x) {return Promise.reject(x)})'>
-!
-
-value: anObject
-"Returns a Promise resolved with anObject."
-<inlineJS: 'return $recv(anObject)._in_(function (x) {return Promise.resolve(x)})'>
+Promise setTraitComposition: {TThenable} asTraitComposition!
 ! !
 
 !JSObjectProxy methodsFor: '*Kernel-Promises'!