这是从此处的其他答案以及链接的民间故事/任务中汲取灵感的另一种方式。我们不会实现复杂的tAll 来处理迭代任务列表和组合任务,而是将关注点分离到单独的函数中。
这是一个简化的tAnd -
const tAnd = (t1, t2) =>
{ const acc = []
const guard = (res, i) => x =>
( acc[i] = x
, acc[0] !== undefined && acc[1] !== undefined
? res (acc)
: void 0
)
return Task
( (res, rej) =>
( t1 .runTask (guard (res, 0), rej) // rej could be called twice!
, t2 .runTask (guard (res, 1), rej) // we'll fix this below
)
)
}
它是这样工作的 -
tAnd
( delay (2000, 'a')
, delay (500, 'b')
)
.runTask (console.log, console.error)
// ~2 seconds later
// [ 'a', 'b' ]
现在 tAll 很容易实现 -
const tAll = (t, ...ts) =>
t === undefined
? tOf ([])
: tAnd (t, tAll (...ts))
Wups,不要忘记沿途变平-
const tAll = (t, ...ts) =>
t === undefined
? tOf ([])
: tMap
( ([ x, xs ]) => [ x, ...xs ]
, tAnd (t, tAll(...ts))
)
它是这样工作的 -
tAll
( delay (2000, 'a')
, delay (500, 'b')
, delay (900, 'c')
, delay (1500, 'd')
, delay (1800, 'e')
, delay (300, 'f')
, delay (2000, 'g')
)
.runTask (console.log, console.error)
// ~2 seconds later
// [ 'a', 'b', 'c', 'd', 'e', 'f', 'g' ]
tAll 也能正确处理错误 -
tAll
( delay (100, 'test failed')
, Task ((_, rej) => rej ('test passed'))
)
.runTask (console.log, console.error)
// test passed
与我们最初的tAll 相比,即使我们限制了我们程序的范围,要正确获得tAnd 也是非常困难的。合并的任务应该只解决一次,或拒绝一次 - 不能同时解决。这意味着还应避免双重解决/拒绝。执行这些约束需要更多代码 -
const tAnd = (t1, t2) =>
{ let resolved = false
let rejected = false
const result = []
const pending = ([ a, b ] = result) =>
a === undefined || b === undefined
const guard = (res, rej, i) =>
[ x =>
( result[i] = x
, resolved || rejected || pending ()
? void 0
: ( resolved = true
, res (result)
)
)
, e =>
resolved || rejected
? void 0
: ( rejected = true
, rej (e)
)
]
return Task
( (res, rej) =>
( t1 .runTask (...guard (res, rej, 0))
, t2 .runTask (...guard (res, rej, 1))
)
)
}
展开下面的sn-p,在自己的浏览器中验证结果-
const Task = k =>
({ runTask: (res, rej) => k (res, rej) })
const tOf = v =>
Task ((res, _) => res (v))
const tMap = (f, t) =>
Task
( (res, rej) =>
t.runTask
( x => res (f (x))
, rej
)
)
const tAnd = (t1, t2) =>
{ let resolved = false
let rejected = false
const result = []
const pending = ([ a, b ] = result) =>
a === undefined || b === undefined
const guard = (res, rej, i) =>
[ x =>
( result[i] = x
, resolved || rejected || pending ()
? void 0
: ( resolved = true
, res (result)
)
)
, e =>
resolved || rejected
? void 0
: ( rejected = true
, rej (e)
)
]
return Task
( (res, rej) =>
( t1 .runTask (...guard (res, rej, 0))
, t2 .runTask (...guard (res, rej, 1))
)
)
}
const tAll = (t, ...ts) =>
t === undefined
? tOf ([])
: tMap
( ([ x, xs ]) => [ x, ...xs ]
, tAnd (t, tAll (...ts))
)
const delay = (ms, x) =>
Task (r => setTimeout (r, ms, x))
tAnd
( delay (2000, 'a')
, delay (500, 'b')
)
.runTask (console.log, console.error)
tAll
( delay (2000, 'a')
, delay (500, 'b')
, delay (900, 'c')
, delay (1500, 'd')
, delay (1800, 'e')
, delay (300, 'f')
, delay (2000, 'g')
)
.runTask (console.log, console.error)
// ~2 seconds later
// [ 'a', 'b' ]
// [ 'a', 'b', 'c', 'd', 'e', 'f', 'g' ]
tAll
( delay (100, 'test failed')
, Task ((_, rej) => rej ('test passed'))
)
.runTask (console.log, console.error)
// Error: test passed
串行处理
最棘手的一点是并行处理要求。如果需求要求 serial 行为,那么实现会非常容易 -
const tAnd = (t1, t2) =>
Task
( (res, rej) =>
t1 .runTask
( a =>
t2 .runTask
( b =>
res ([ a, b ])
, rej
)
, rej
)
)
当然,tAll 的实现保持不变。注意现在的延迟差异,因为现在任务是按顺序运行的 -
tAnd
( delay (2000, 'a')
, delay (500, 'b')
)
.runTask (console.log, console.error)
// ~2.5 seconds later
// [ 'a', 'b' ]
还有tAll 的许多任务-
tAll
( delay (2000, 'a')
, delay (500, 'b')
, delay (900, 'c')
, delay (1500, 'd')
, delay (1800, 'e')
, delay (300, 'f')
, delay (2000, 'g')
)
.runTask (console.log, console.error)
// ~ 9 seconds later
// [ 'a', 'b', 'c', 'd', 'e', 'f', 'g' ]
展开下面的sn-p,在自己的浏览器中验证结果-
const Task = k =>
({ runTask: (res, rej) => k (res, rej) })
const tOf = v =>
Task ((res, _) => res (v))
const tMap = (f, t) =>
Task
( (res, rej) =>
t.runTask
( x => res (f (x))
, rej
)
)
const tAnd = (t1, t2) =>
Task
( (res, rej) =>
t1 .runTask
( a =>
t2 .runTask
( b =>
res ([ a, b ])
, rej
)
, rej
)
)
const tAll = (t, ...ts) =>
t === undefined
? tOf ([])
: tMap
( ([ x, xs ]) => [ x, ...xs ]
, tAnd (t, tAll (...ts))
)
const delay = (ms, x) =>
Task (r => setTimeout (r, ms, x))
tAnd
( delay (2000, 'a')
, delay (500, 'b')
)
.runTask (console.log, console.error)
// ~2.5 seconds later
// [ 'a', 'b' ]
tAll
( delay (2000, 'a')
, delay (500, 'b')
, delay (900, 'c')
, delay (1500, 'd')
, delay (1800, 'e')
, delay (300, 'f')
, delay (2000, 'g')
)
.runTask (console.log, console.error)
// ~ 9 seconds later
// [ 'a', 'b', 'c', 'd', 'e', 'f', 'g' ]
tAll
( delay (100, 'test failed')
, Task ((_, rej) => rej ('test passed'))
)
.runTask (console.log, console.error)
// Error: test passed
如何实现tOr和tRace
为了完整起见,这里是tOr。注意tOr这里相当于folktale的Task.concat-
const tOr = (t1, t2) =>
{ let resolved = false
let rejected = false
const guard = (res, rej) =>
[ x =>
resolved || rejected
? void 0
: ( resolved = true
, res (x)
)
, e =>
resolved || rejected
? void 0
: ( rejected = true
, rej (e)
)
]
return Task
( (res, rej) =>
( t1 .runTask (...guard (res, rej))
, t2 .runTask (...guard (res, rej))
)
)
}
解决或拒绝两个任务中最先完成的任务 -
tOr
( delay (2000, 'a')
, delay (500, 'b')
)
.runTask (console.log, console.error)
// ~500 ms later
// 'b'
还有tRace-
const tRace = (t = tOf (undefined), ...ts) =>
ts .reduce (tOr, t)
解决或拒绝许多任务中最先完成的任务 -
tRace
( delay (2000, 'a')
, delay (500, 'b')
, delay (900, 'c')
, delay (1500, 'd')
, delay (1800, 'e')
, delay (300, 'f')
, delay (2000, 'g')
)
.runTask (console.log, console.error)
// ~300 ms later
// 'f'
展开下面的sn-p,在自己的浏览器中验证结果-
const Task = k =>
({ runTask: (a, b) => k (a, b) })
const tOr = (t1, t2) =>
{ let resolved = false
let rejected = false
const guard = (res, rej) =>
[ x =>
resolved || rejected
? void 0
: ( resolved = true
, res (x)
)
, e =>
resolved || rejected
? void 0
: ( rejected = true
, rej (e)
)
]
return Task
( (res, rej) =>
( t1 .runTask (...guard (res, rej))
, t2 .runTask (...guard (res, rej))
)
)
}
const tRace = (t = tOf (undefined), ...ts) =>
ts. reduce (tOr, t)
const delay = (ms, x) =>
Task (r => setTimeout (r, ms, x))
tOr
( delay (2000, 'a')
, delay (500, 'b')
)
.runTask (console.log, console.error)
// ~500 ms later
// 'b'
tRace
( delay (2000, 'a')
, delay (500, 'b')
, delay (900, 'c')
, delay (1500, 'd')
, delay (1800, 'e')
, delay (300, 'f')
, delay (2000, 'g')
)
.runTask (console.log, console.error)
// ~300 ms later
// note `f` appears in the output first because this tRace demo finishes before the tOr demo above
// 'f'
tRace
( delay (100, 'test failed')
, Task ((_, rej) => rej ('test passed'))
)
.runTask (console.log, console.error)
// Error: test passed
如何实现tAp
在 cmets 中,我们谈论的是 applicative,tAp。我认为tAll 使实现变得相当容易-
const tAp = (f, ...ts) =>
tMap
( ([ f, ...xs ]) => f (...xs)
, tAll (f, ...ts)
)
tAp 接受一个任务包装函数和任意数量的任务包装值,并返回一个新任务 -
const sum = (v, ...vs) =>
vs.length === 0
? v
: v + sum (...vs)
tAp
( delay (2000, sum)
, delay (500, 1)
, delay (900, 2)
, delay (1500, 3)
, delay (1800, 4)
, delay (300, 5)
)
.runTask (console.log, console.error)
// ~2 seconds later
// 15
除非任务有副作用,否则我看不出tAp 的“并行”实现违反应用法则的原因。
展开下面的sn-p,在自己的浏览器中验证结果-
const Task = k =>
({ runTask: (res, rej) => k (res, rej) })
const tOf = v =>
Task ((res, _) => res (v))
const tMap = (f, t) =>
Task
( (res, rej) =>
t.runTask
( x => res (f (x))
, rej
)
)
const tAp = (f, ...ts) =>
tMap
( ([ f, ...xs ]) => f (...xs)
, tAll (f, ...ts)
)
const tAnd = (t1, t2) =>
{ let resolved = false
let rejected = false
const result = []
const pending = ([ a, b ] = result) =>
a === undefined || b === undefined
const guard = (res, rej, i) =>
[ x =>
( result[i] = x
, resolved || rejected || pending ()
? void 0
: ( resolved = true
, res (result)
)
)
, e =>
resolved || rejected
? void 0
: ( rejected = true
, rej (e)
)
]
return Task
( (res, rej) =>
( t1 .runTask (...guard (res, rej, 0))
, t2 .runTask (...guard (res, rej, 1))
)
)
}
const tAll = (t, ...ts) =>
t === undefined
? tOf ([])
: tMap
( ([ x, xs ]) => [ x, ...xs ]
, tAnd (t, tAll (...ts))
)
const delay = (ms, x) =>
Task (r => setTimeout (r, ms, x))
const sum = (v, ...vs) =>
vs.length === 0
? v
: v + sum (...vs)
tAp
( delay (2000, sum)
, delay (500, 1)
, delay (900, 2)
, delay (1500, 3)
, delay (1800, 4)
, delay (300, 5)
)
.runTask (console.log, console.error)
// ~2 seconds later
// 15