diff --git a/src/data/conversions/promise-to-future.js b/src/data/conversions/promise-to-future.js new file mode 100644 index 0000000..29b727b --- /dev/null +++ b/src/data/conversions/promise-to-future.js @@ -0,0 +1,35 @@ +//---------------------------------------------------------------------- +// +// This source file is part of the Folktale project. +// +// Licensed under MIT. See LICENCE for full licence information. +// See CONTRIBUTORS for the list of contributors to the project. +// +//---------------------------------------------------------------------- + +const { Cancelled } = require('folktale/data/future/_execution-state'); +const Deferred = require('folktale/data/future/_deferred'); + +/*~ + * stability: experimental + * type: | + * forall e, v: + * (Promise v e) => Future e v + */ +const promiseToFuture = (aPromise) => { + const deferred = new Deferred(); + aPromise.then( + (value) => deferred.resolve(value), + (error) => { + if (Cancelled.hasInstance(error)) { + deferred.cancel(); + } else { + deferred.reject(error); + } + } + ) + return deferred.future(); +}; + + +module.exports = promiseToFuture; diff --git a/src/data/future/_future.js b/src/data/future/_future.js index fe48183..70913a5 100644 --- a/src/data/future/_future.js +++ b/src/data/future/_future.js @@ -247,6 +247,16 @@ Object.assign(Future, { let result = new Future(); // eslint-disable-line prefer-const result._state = Rejected(reason); return result; + }, + + + /*~ + * stability: experimental + * type: | + * forall e, v: (Promise v e) => Future e v + */ + fromPromise(aPromise) { + return require('folktale/data/conversions/promise-to-future')(aPromise); } }); diff --git a/src/data/future/index.js b/src/data/future/index.js index 48d02f8..3a7a654 100644 --- a/src/data/future/index.js +++ b/src/data/future/index.js @@ -16,6 +16,7 @@ const Future = require('./_future'); module.exports = { of: Future.of, rejected: Future.rejected, + fromPromise: Future.fromPromise, _Deferred: require('./_deferred'), _ExecutionState: require('./_execution-state'), _Future: Future diff --git a/test/specs-src/data.future.es6 b/test/specs-src/data.future.es6 index d670ce5..40012c7 100644 --- a/test/specs-src/data.future.es6 +++ b/test/specs-src/data.future.es6 @@ -16,6 +16,39 @@ const { _ExecutionState, _Deferred: Deferred } = Future; const { Resolved, Rejected } = _ExecutionState; describe('Data.Future', function() { + describe('conversions', () => { + property('Promise.resolve(v) → Future.resolve(v)', 'nat', (a) => { + return new Promise((ok, error) => { + Future.fromPromise(Promise.resolve(a)).listen({ + onResolved: (v) => v === a ? ok(true) : error(`Assertion failed: ${a} === ${v}`), + onRejected: (v) => error(`Expected a resolved future with ${a}, got a rejected future with ${v}`), + onCancelled: () => error(`Expected a resolved future with ${a}, got a cancelled future`) + }); + }); + }); + + property('Promise.reject(v) → Future.reject(v)', 'nat', (a) => { + return new Promise((ok, error) => { + Future.fromPromise(Promise.reject(a)).listen({ + onRejected: (v) => v === a ? ok(true) : error(`Assertion failed: ${a} === ${v}`), + onResolved: (v) => error(`Expected a rejected future with ${a}, got a resolved future with ${v}`), + onCancelled: () => error(`Expected a rejected future with ${a}, got a cancelled future`) + }); + }); + }); + + property('Promise.reject(Cancelled()) → Future.cancel()', () => { + return new Promise((ok, error) => { + Future.fromPromise(Promise.reject(_ExecutionState.Cancelled())).listen({ + onRejected: (v) => error(`Expected a cancelled future, got a rejected future with ${v}`), + onResolved: (v) => error(`Expected a cancelled future, got a resolved future with ${v}`), + onCancelled: () => ok(true) + }); + }); + }); + }); + + describe('Deferreds', function() { it('new Deferred() should start in a pending state', () => { let deferred = new Deferred();