Repository: rtfeldman/seamless-immutable (js)

Issue:
issue while working with Promise
```
const names = [{name: "Bob"}, {name: "Foo"}];
console.log("names:" + JSON.stringify(names));

Promise.all(
    names.map(n => {
        return Promise.resolve(n).then(n => {
            console.log("n:" + JSON.stringify(n));
            return n;
        })
    })
).then(namesBack => {
    console.log("namesBack:" + JSON.stringify(namesBack));
});
```

The code above results in this:

> names:[{"name":"Bob"},{"name":"Foo"}]
> n:{"name":"Bob"}
> n:{"name":"Foo"}
> namesBack:[{"name":"Bob"},{"name":"Foo"}]

However If you only change the first line to use immutable like this: 

```
const names = Immutable.from([{name: "Bob"}, {name: "Foo"}]);
console.log("names:" + JSON.stringify(names));

Promise.all(
    names.map(n => {
        return Promise.resolve(n).then(n => {
            console.log("n:" + JSON.stringify(n));
            return n;
        })
    })
).then(namesBack => {
    console.log("namesBack:" + JSON.stringify(namesBack));
});
```

The result differs:

> names:[{"name":"Bob"},{"name":"Foo"}]
> n:{"name":"Bob"}
> n:{"name":"Foo"}
> namesBack:[{"_45":0,"_81":0,"_65":null,"_54":null},{"_45":0,"_81":0,"_65":null,"_54":null}]

If we call asMutable() before .map call, then we are fine:

```
const names = Immutable.from([{name: "Bob"}, {name: "Foo"}]);
console.log("names:" + JSON.stringify(names));

Promise.all(
    names.asMutable().map(n => {
        return Promise.resolve(n).then(n => {
            console.log("n:" + JSON.stringify(n));
            return n;
        })
    })
).then(namesBack => {
    console.log("namesBack:" + JSON.stringify(namesBack));
});
```

result:

> names:[{"name":"Bob"},{"name":"Foo"}]
> n:{"name":"Bob"}
> n:{"name":"Foo"}
> namesBack:[{"name":"Bob"},{"name":"Foo"}]

We have no intent to mutate anything, so there should not be a need to call **asMutable**. 
An enhancement to the "seamless-immutable" would be good.


Pull request:
Handle promises
Don’t make promises immutable, but make their fulfillment value immutable.
Fixes #153 

Patch:
diff --git a/seamless-immutable.development.js b/seamless-immutable.development.js
index d17773e..49c4b2d 100644
--- a/seamless-immutable.development.js
+++ b/seamless-immutable.development.js
@@ -606,9 +606,16 @@ function immutableInit(config) {
            obj instanceof File;
   }
 
+  function isPromise(obj) {
+    return typeof obj === 'object' &&
+           typeof obj.then === 'function';
+  }
+
   function Immutable(obj, options, stackRemaining) {
     if (isImmutable(obj) || isReactElement(obj) || isFileObject(obj)) {
       return obj;
+    } else if (isPromise(obj)) {
+      return obj.then(Immutable);
     } else if (Array.isArray(obj)) {
       return makeImmutableArray(obj.slice());
     } else if (obj instanceof Date) {
diff --git a/src/seamless-immutable.js b/src/seamless-immutable.js
index 67814dc..d4ef975 100644
--- a/src/seamless-immutable.js
+++ b/src/seamless-immutable.js
@@ -606,9 +606,16 @@ function immutableInit(config) {
            obj instanceof File;
   }
 
+  function isPromise(obj) {
+    return typeof obj === 'object' &&
+           typeof obj.then === 'function';
+  }
+
   function Immutable(obj, options, stackRemaining) {
     if (isImmutable(obj) || isReactElement(obj) || isFileObject(obj)) {
       return obj;
+    } else if (isPromise(obj)) {
+      return obj.then(Immutable);
     } else if (Array.isArray(obj)) {
       return makeImmutableArray(obj.slice());
     } else if (obj instanceof Date) {


Test patch:
diff --git a/test/Immutable.spec.js b/test/Immutable.spec.js
index d8820d7..867e7f7 100644
--- a/test/Immutable.spec.js
+++ b/test/Immutable.spec.js
@@ -72,6 +72,26 @@ var getTestUtils = require("./TestUtils.js");
         })
       });
 
+      it("makes the promise fulfillment value immutable, but not the promise itself", function() {
+        var promise = Promise.resolve([1,2,3]);
+        var wrappedPromise = Immutable(promise);
+
+        wrappedPromise.then(result => {
+          assert.isTrue(Immutable.isImmutable(result), 'The promise fulfillment value should be immutable');
+          assert.isFalse(Immutable.isImmutable(wrappedPromise), 'The promise itself should not be immutable');
+        });
+      });
+
+      it("doesn't wrap the promise rejection reason", function() {
+        var reason = new Error('foo');
+        var promise = Promise.reject(reason);
+        var wrappedPromise = Immutable(promise);
+
+        wrappedPromise.catch(catchedReason => {
+          assert.strictEqual(reason, catchedReason, 'The promise rejection reason should be left untouched');
+        });
+      });
+
       it("doesn't fail when a function is defined on Array.prototype", function() {
         Array.prototype.veryEvilFunction = function() {};
         Immutable([]);