Browse Source

Promises: exceptions in then/catch functions not caught by ST

Herbert Vojčík 8 years ago
parent
commit
e796499b0a
4 changed files with 40 additions and 24 deletions
  1. 2 0
      API-CHANGES.txt
  2. 14 14
      src/Kernel-Promises.js
  3. 10 10
      src/Kernel-Promises.st
  4. 14 0
      support/boot.js

+ 2 - 0
API-CHANGES.txt

@@ -19,6 +19,8 @@
   + then:catch:
   + then:on:do:
   + then:on:do:catch:
++ amber/boot api
+  + seamless
 
 
 0.14.18:

+ 14 - 14
src/Kernel-Promises.js

@@ -17,9 +17,9 @@ var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return self.then(null, function (err) {
+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,"catch:",{aBlock:aBlock},$globals.Thenable)});
@@ -27,7 +27,7 @@ return self;
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aBlock"],
-source: "catch: aBlock\x0a<return self.then(null, function (err) {\x0a    return aBlock._value_(err);\x0a})>",
+source: "catch: aBlock\x0a<return self.then(null, function (err) {return $core.seamless(function () {\x0a    return aBlock._value_(err);\x0a})})>",
 referencedClasses: [],
 //>>excludeEnd("ide");
 messageSends: []
@@ -43,10 +43,10 @@ var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return self.then(null, function (err) {
+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,"on:do:",{aClass:aClass,aBlock:aBlock},$globals.Thenable)});
@@ -54,7 +54,7 @@ return self;
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aClass", "aBlock"],
-source: "on: aClass do: aBlock\x0a<return self.then(null, function (err) {\x0a    if (err._isKindOf_(aClass)) return aBlock._value_(err);\x0a    else throw err;\x0a})>",
+source: "on: aClass do: aBlock\x0a<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: []
@@ -70,10 +70,10 @@ var self=this;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 return $core.withContext(function($ctx1) {
 //>>excludeEnd("ctx");
-return self.then(null, function (err) {
+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;
 //>>excludeStart("ctx", pragmas.excludeDebugContexts);
 }, function($ctx1) {$ctx1.fill(self,"on:do:catch:",{aClass:aClass,aBlock:aBlock,anotherBlock:anotherBlock},$globals.Thenable)});
@@ -81,7 +81,7 @@ return self;
 },
 //>>excludeStart("ide", pragmas.excludeIdeData);
 args: ["aClass", "aBlock", "anotherBlock"],
-source: "on: aClass do: aBlock catch: anotherBlock\x0a<return self.then(null, function (err) {\x0a    try { if (err._isKindOf_(aClass)) return aBlock._value_(err); } catch (e) { err = e; }\x0a    return anotherBlock._value_(err);\x0a})>",
+source: "on: aClass do: aBlock catch: anotherBlock\x0a<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})})>",
 referencedClasses: [],
 //>>excludeEnd("ide");
 messageSends: []
@@ -101,16 +101,16 @@ return $core.withContext(function($ctx1) {
 var array = Array.isArray(aBlockOrArray) ? aBlockOrArray : [aBlockOrArray];
 return array.reduce(function (soFar, aBlock) {
     return soFar.then(typeof aBlock === "function" && aBlock.length > 1 ?
-        function (result) {
+        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) {
+        })} :
+        function (result) {return $core.seamless(function () {
             return aBlock._value_(result);
-        }
+        })}
     );
 }, self);
 return self;
@@ -120,7 +120,7 @@ return self;
 },
 //>>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<\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) {\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) {\x0a            return aBlock._value_(result);\x0a        }\x0a    );\x0a}, self)>",
+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<\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: []

+ 10 - 10
src/Kernel-Promises.st

@@ -12,23 +12,23 @@ I contain methods that wrap Promises/A+ `.then` behaviour.!
 !Thenable methodsFor: 'promises'!
 
 catch: aBlock
-<return self.then(null, function (err) {
+<return self.then(null, function (err) {return $core.seamless(function () {
     return aBlock._value_(err);
-})>
+})})>
 !
 
 on: aClass do: aBlock
-<return self.then(null, function (err) {
+<return self.then(null, function (err) {return $core.seamless(function () {
     if (err._isKindOf_(aClass)) return aBlock._value_(err);
     else throw err;
-})>
+})})>
 !
 
 on: aClass do: aBlock catch: anotherBlock
-<return self.then(null, function (err) {
+<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);
-})>
+})})>
 !
 
 then: aBlockOrArray
@@ -44,16 +44,16 @@ The first argument always contains the result as-is."
 var array = Array.isArray(aBlockOrArray) ? aBlockOrArray : [aBlockOrArray];
 return array.reduce(function (soFar, aBlock) {
     return soFar.then(typeof aBlock === "function" && aBlock.length >> 1 ?
-        function (result) {
+        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) {
+        })} :
+        function (result) {return $core.seamless(function () {
             return aBlock._value_(result);
-        }
+        })}
     );
 }, self)>
 !

+ 14 - 0
support/boot.js

@@ -924,6 +924,20 @@ define(['require', './compatibility'], function (require) {
             }
         };
 
+        /*
+           Runs worker function so that error handler is not set up
+           if there isn't one. This is accomplished by unconditional
+           wrapping inside a context of a simulated `nil seamlessDoIt` call,
+           which then stops error handler setup (see st.withContext above).
+           The effect is, $core.seamless(fn)'s exceptions are not
+           handed into ST error handler and caller should process them.
+         */
+        st.seamless = function (worker) {
+            return inContext(worker, function (ctx) {
+                ctx.fill(nil, "seamlessDoIt", {}, globals.UndefinedObject);
+            });
+        };
+
         function inContextWithErrorHandling(worker, setup) {
             try {
                 return inContext(worker, setup);