From 71c1a9b3e216fa6d8819702b207a21572cdf4b35 Mon Sep 17 00:00:00 2001 From: tshemsedinov Date: Mon, 10 Jun 2019 01:57:54 +0300 Subject: [PATCH 1/3] Implement Future stateless and lazy Promise analogue Refs: https://github.com/metarhia/metasync/issues/431 --- lib/future.js | 45 ++++++++++++++++++++++++ metasync.js | 1 + test/future.js | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 lib/future.js create mode 100644 test/future.js diff --git a/lib/future.js b/lib/future.js new file mode 100644 index 00000000..cec235c4 --- /dev/null +++ b/lib/future.js @@ -0,0 +1,45 @@ +'use strict'; + +class Future { + constructor(executor) { + this.executor = executor; + } + + static of(value) { + return new Future(resolve => resolve(value)); + } + + chain(fn) { + return new Future((resolve, reject) => + this.fork( + value => fn(value).fork(resolve, reject), + error => reject(error) + ) + ); + } + + map(fn) { + return this.chain( + value => + new Future((resolve, reject) => { + try { + resolve(fn(value)); + } catch (error) { + reject(error); + } + }) + ); + } + + fork(successed, failed) { + this.executor(successed, failed); + } + + promise() { + return new Promise((resolve, reject) => { + this.fork(value => resolve(value), error => reject(error)); + }); + } +} + +module.exports = { Future }; diff --git a/metasync.js b/metasync.js index a51f620a..97d4e68e 100644 --- a/metasync.js +++ b/metasync.js @@ -12,6 +12,7 @@ const submodules = [ 'control', // Control flow utilities 'do', // Simple chain/do 'fp', // Async utils for functional programming + 'future', // Future stateless and lazy Promise analogue 'memoize', // Async memoization 'poolify', // Create pool from factory 'queue', // Concurrent queue diff --git a/test/future.js b/test/future.js new file mode 100644 index 00000000..ba13dbfa --- /dev/null +++ b/test/future.js @@ -0,0 +1,92 @@ +'use strict'; + +const { Future } = require('..'); +const metatests = require('metatests'); + +metatests.test('Future map/fork', async test => { + Future.of(3) + .map(x => x ** 2) + .fork(value => { + test.strictSame(value, 9); + test.end(); + }); +}); + +metatests.test('Future lazy', async test => { + Future.of(3).map(test.mustNotCall()); + test.end(); +}); + +metatests.test('Future resolve', async test => { + new Future(resolve => { + setTimeout(() => { + resolve(5); + }, 0); + }).fork(value => { + test.strictSame(value, 5); + test.end(); + }, test.mustNotCall()); +}); + +metatests.test('Future reject', async test => { + new Future((resolve, reject) => { + reject(new Error('msg')); + }).fork(test.mustNotCall(), error => { + test.strictSame(error.message, 'msg'); + test.end(); + }); +}); + +metatests.test('Future promise', async test => { + const value = await Future.of(6) + .map(x => ++x) + .map(x => x ** 3) + .promise(); + + test.strictSame(value, 343); + test.end(); +}); + +metatests.test('Future catch', async test => { + Future.of(6) + .map(() => { + throw new Error('msg'); + }) + .fork(test.mustNotCall, error => { + test.strictSame(error.message, 'msg'); + test.end(); + }); +}); + +metatests.test('Future stateless', async test => { + const f1 = Future.of(3); + const f2 = f1.map(x => ++x); + const f3 = f2.map(x => x ** 3); + const f4 = f1.map(x => x * 2); + + f1.fork(value => { + test.strictSame(value, 3); + }); + + f1.fork(value => { + test.strictSame(value, 3); + }); + + f2.fork(value => { + test.strictSame(value, 4); + }); + + f2.fork(value => { + test.strictSame(value, 4); + }); + + f3.fork(value => { + test.strictSame(value, 64); + }); + + f4.fork(value => { + test.strictSame(value, 6); + }); + + test.end(); +}); From 65ec4dc7d5ca43343f61e331ec308b027eb46a04 Mon Sep 17 00:00:00 2001 From: tshemsedinov Date: Tue, 11 Jun 2019 02:16:26 +0300 Subject: [PATCH 2/3] Implement futurify callback-last to future-returning Closes: https://github.com/metarhia/metasync/issues/431 --- lib/adapters.js | 15 +++++++++++++++ test/future.js | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/lib/adapters.js b/lib/adapters.js index 31c946dd..8857e1c6 100644 --- a/lib/adapters.js +++ b/lib/adapters.js @@ -1,5 +1,7 @@ 'use strict'; +const { Future } = require('./future'); + // Convert Promise to callback-last // promise // callback @@ -67,10 +69,23 @@ const promisifySync = fn => (...args) => { return Promise.resolve(result); }; +// Convert callback contract to Future-returning function +// fn callback-last function +// +// Returns: Future-returning function +const futurify = fn => (...args) => + new Future((resolve, reject) => { + fn(...args, (err, data) => { + if (err) reject(err); + else resolve(data); + }); + }); + module.exports = { callbackify, asyncify, promiseToCallbackLast, promisify, promisifySync, + futurify, }; diff --git a/test/future.js b/test/future.js index ba13dbfa..a056772d 100644 --- a/test/future.js +++ b/test/future.js @@ -1,6 +1,6 @@ 'use strict'; -const { Future } = require('..'); +const { Future, futurify } = require('..'); const metatests = require('metatests'); metatests.test('Future map/fork', async test => { @@ -90,3 +90,37 @@ metatests.test('Future stateless', async test => { test.end(); }); + +metatests.test('Future futurify success', async test => { + const f1 = (a, b, callback) => { + if (typeof a !== 'number' || typeof b !== 'number') { + callback(new Error('Arguments must be numbers')); + return; + } + callback(null, a + b); + }; + + const f2 = futurify(f1); + + f2(10, 20).fork(value => { + test.strictSame(value, 30); + test.end(); + }); +}); + +metatests.test('Future futurify fail', async test => { + const f1 = (a, b, callback) => { + if (typeof a !== 'number' || typeof b !== 'number') { + callback(new Error('Arguments must be numbers')); + return; + } + callback(null, a + b); + }; + + const f2 = futurify(f1); + + f2('10', '20').fork(test.mustNotCall, error => { + test.strictSame(error.message, 'Arguments must be numbers'); + test.end(); + }); +}); From 7b893dd1af1ef5427193de0d715e7ba24229e063 Mon Sep 17 00:00:00 2001 From: tshemsedinov Date: Thu, 13 Jun 2019 15:47:44 +0300 Subject: [PATCH 3/3] Add Future.err() and rename Future.fork to .run --- lib/future.js | 13 +++++++------ test/future.js | 33 ++++++++++++++++++++------------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/lib/future.js b/lib/future.js index cec235c4..2e563531 100644 --- a/lib/future.js +++ b/lib/future.js @@ -9,12 +9,13 @@ class Future { return new Future(resolve => resolve(value)); } + static err(error) { + return new Future((resolve, reject) => reject(error)); + } + chain(fn) { return new Future((resolve, reject) => - this.fork( - value => fn(value).fork(resolve, reject), - error => reject(error) - ) + this.run(value => fn(value).run(resolve, reject), error => reject(error)) ); } @@ -31,13 +32,13 @@ class Future { ); } - fork(successed, failed) { + run(successed, failed) { this.executor(successed, failed); } promise() { return new Promise((resolve, reject) => { - this.fork(value => resolve(value), error => reject(error)); + this.run(value => resolve(value), error => reject(error)); }); } } diff --git a/test/future.js b/test/future.js index a056772d..59e2ea9a 100644 --- a/test/future.js +++ b/test/future.js @@ -3,10 +3,10 @@ const { Future, futurify } = require('..'); const metatests = require('metatests'); -metatests.test('Future map/fork', async test => { +metatests.test('Future map/run', async test => { Future.of(3) .map(x => x ** 2) - .fork(value => { + .run(value => { test.strictSame(value, 9); test.end(); }); @@ -22,7 +22,7 @@ metatests.test('Future resolve', async test => { setTimeout(() => { resolve(5); }, 0); - }).fork(value => { + }).run(value => { test.strictSame(value, 5); test.end(); }, test.mustNotCall()); @@ -31,7 +31,14 @@ metatests.test('Future resolve', async test => { metatests.test('Future reject', async test => { new Future((resolve, reject) => { reject(new Error('msg')); - }).fork(test.mustNotCall(), error => { + }).run(test.mustNotCall(), error => { + test.strictSame(error.message, 'msg'); + test.end(); + }); +}); + +metatests.test('Future error', async test => { + Future.err(new Error('msg')).run(test.mustNotCall(), error => { test.strictSame(error.message, 'msg'); test.end(); }); @@ -52,7 +59,7 @@ metatests.test('Future catch', async test => { .map(() => { throw new Error('msg'); }) - .fork(test.mustNotCall, error => { + .run(test.mustNotCall, error => { test.strictSame(error.message, 'msg'); test.end(); }); @@ -64,27 +71,27 @@ metatests.test('Future stateless', async test => { const f3 = f2.map(x => x ** 3); const f4 = f1.map(x => x * 2); - f1.fork(value => { + f1.run(value => { test.strictSame(value, 3); }); - f1.fork(value => { + f1.run(value => { test.strictSame(value, 3); }); - f2.fork(value => { + f2.run(value => { test.strictSame(value, 4); }); - f2.fork(value => { + f2.run(value => { test.strictSame(value, 4); }); - f3.fork(value => { + f3.run(value => { test.strictSame(value, 64); }); - f4.fork(value => { + f4.run(value => { test.strictSame(value, 6); }); @@ -102,7 +109,7 @@ metatests.test('Future futurify success', async test => { const f2 = futurify(f1); - f2(10, 20).fork(value => { + f2(10, 20).run(value => { test.strictSame(value, 30); test.end(); }); @@ -119,7 +126,7 @@ metatests.test('Future futurify fail', async test => { const f2 = futurify(f1); - f2('10', '20').fork(test.mustNotCall, error => { + f2('10', '20').run(test.mustNotCall, error => { test.strictSame(error.message, 'Arguments must be numbers'); test.end(); });