今天做题遇到题目用到了data.task
这个包,里面的Task
实现很巧妙,类似Promise
但又不是,简单对比分析一下。
Task 使用方法
当时看这个代码看入迷了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
var getPost = function (i) { return new Task(function (rej, res) { setTimeout(function () { res({ id: i, title: "Love them tasks" }); }, 300); }); };
var getComments = function (i) { return new Task(function (rej, res) { setTimeout(function () { res([ { post_id: i, body: "This book should be illegal" }, { post_id: i, body: "Monads are like smelly shallots" }, ]); }, 300); }); };
const getCommentsById = _.compose(getComments, _.prop("id")); var ex3 = _.compose(_.chain(getCommentsById), getPost);
it("Exercise 3", function (done) { E.ex3(13).fork(console.log, function (res) { assert.deepEqual(res.map(_.prop("post_id")), [13, 13]); done(); }); });
|
这个是可以跑得,并且通过了测试,相信你的眼睛。
好奇getPost
一个异步任务怎么可能将两个函数衔接的如此流畅并且一点副作用没有!
其实这类似RXjs
的网络流,处理函数都在后面等着数据流。
Task 源码
于是点开Task
的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| module.exports = Task;
function Task(computation, cleanup) { this.fork = computation; this.cleanup = cleanup || function() {}; }
Task.prototype.of = function _of(b) { return new Task(function(_, resolve) { return resolve(b); }); }; Task.prototype.rejected = function _rejected(a) { return new Task(function(reject) { return reject(a); }); };
Task.prototype.map = function _map(f) { var fork = this.fork; var cleanup = this.cleanup;
return new Task(function(reject, resolve) { return fork(function(a) { return reject(a); }, function(b) { return resolve(f(b)); }); }, cleanup); };
Task.prototype.chain = function _chain(f) { var fork = this.fork; var cleanup = this.cleanup; return new Task(function(reject, resolve) { return fork(function(a) { return reject(a); }, function(b) { return f(b).fork(reject, resolve); }); }, cleanup); };
|
手动实现
思路分析:
刚才看源码了,把传入的函数保存起来,推迟到实例化对象调用fork之后执行回调,然后resolve一连串都跑起来了,太秀了
实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class STask { fork = () => {}; constructor(cb) { this.fork = cb; } concat(f) { const thisTask = this.fork; return new STask(function (res, rej) { thisTask( function (r) { f(r).fork(res, rej); }, function (e) { rej(e); } ); }); } }
|
不敢相信,竟然几行代码写出来的Task
看看能不能用?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| let i = 5; setInterval(() => console.log(i--), 1000);
const find = () => new STask((resolve, reject) => { setTimeout(() => { resolve("函数式编程指北"); }, 3000); }); const read = (book) => new STask((resolve, reject) => { setTimeout(() => { resolve("I am reading " + book); }, 3000); });
const readBook = find().concat(read); readBook.fork(function (res) { console.log("success:", res); });
|
总结
简单实现了两个任务拼接而已。。
上面说到,fork
时会将在构造函数传入的callback
执行,但Promise
不是,它会在创建时就执行,与其对应的then
仅仅是注册成功/失败回调
Promise
是 pubsub
模式,而 data.task
这个是利用continuation-passing-style
By example: Continuation-passing style in JavaScript (might.net)