翻译连载 | 第 10 章:异步的函数式(上)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
2017-09-29 16:58
465 查看
第 10 章:异步的函数式(上)
阅读到这里,你已经学习了我所说的所有轻量级函数式编程的基础概念,在本章节中,我们将把这些概念应有到不同的情景当中,但绝对不会有新的知识点。到目前为止,我们所说的一切都是同步的,意味着我们调用函数,传入参数后马上就会得到返回值。大部分的情况下是没问题的,但这几乎满足不了现有的 JS 应用。为了能在当前的 JS 环境里使用上函数式编程,我们需要去了解异步的函数式编程。
本章的目的是拓展我们对用函数式编程管理数据的思维,以便之后我们在更多的业务上应用。
时间状态
在你所有的应用里,最复杂的状态就是时间。当你操作的数据状态改变过程比较直观的时候,是很容易管理的。但是,如果状态随着时间因为响应事件而隐晦的变化,管理这些状态的难度将会成几何级增长。我们在本文中介绍的函数式编程可以让代码变得更可读,从而增强了可靠性和可预见性。但是当你添加异步操作到你的项目里的时候,这些优势将会大打折扣。
必须明确的一点是:并不是说一些操作不能用同步来完成,或者触发异步行为很容易。协调那些可能会改变应用程序的状态的响应,这需要大量额外的工作。
所以,作为作者的你最好付出一些努力,或者只是留给阅读你代码的人一个难题,去弄清楚如果 A 在 B 之前完成,项目中状态是什么,还有相反的情况是什么?这是一个浮夸的问题,但以我的观点来看,这有一个确切的答案:如果可以把复杂的代码变得更容易理解,作者就必须花费更多心思。
减少时间状态
异步编程最为重要的一点是通过抽象时间来简化状态变化的管理。为说明这一点,让我们先来看下一种有竞争状态(又称,时间复杂度)的糟糕情况,且必须手动去管理里面的状态:
var customerId = 42; var customer; lookupCustomer( customerId, function onCustomer(customerRecord){ var orders = customer ? customer.orders : null; customer = customerRecord; if (orders) { customer.orders = orders; } } ); lookupOrders( customerId, function onOrders(customerOrders){ if (!customer) { customer = {}; } customer.orders = customerOrders; } );
回调函数
onCustomer(..)和
onOrders(..)之间是互为竞争关系。假设他们都在运行,两者都有可能先运行,那将无法预测到会发生什么。
如果我们可以把
lookupOrders(..)写到
onCustomer(..)里面,那我们就可以确认
onOrders(..)会在
onCustomer(..)之后运行,但我们不能这么做,因为我们需要让
2 个查询同时执行。
所以,为了让这个基于时间的复杂状态正常化,我们用相应的
if-声明在各自的回调函数里来检查外部作用域的变量
customer。当各自的回调函数被执行,将会去检测
customer的状态,从而确定各自的执行顺序,如果
customer在回调函数里还没被定义,那他就是先运行的,否则则是第二个运行的。
这些代码可以运行,但是他违背了可读性的原则。时间复杂度让这个代码变得难以阅读。
让我们改用 JS promise 来把时间因素抽离出来:
var customerId = 42; var customerPromise = lookupCustomer( customerId ); var ordersPromise = lookupOrders( customerId ); customerPromise.then( function onCustomer(customer){ ordersPromise.then( function onOrders(orders){ customer.orders = orders; } ); } );
现在
onOrders(..)回调函数存在
onCustomer(..)回调函数里,所以他们各自的执行顺序是可以保证的。在各自的
then(..)运行之前
lookupCustomer(..)和
lookupOrders(..)被分别的调用,两个查询就已经并行的执行完了。
这可能不太明显,但是这个代码里还有其他内在的竞争状态,那就是 promise 的定义没有被体现出来。如果
orders的查询在把
onOrders(..)回调函数被
ordersPromise.then(..)调用前完成,那么就需要一些比较智能的 东西 来保存
orders直到
onOrders(..)能被调用。
同理,
record(或者说
customer)对象是否能在
onCustomer(..)执行时被接收到。
这里的 东西 和我们之前讨论过的时间复杂度类似。但我们不必去担心这些复杂性,无论是编码或者是读(更为重要)这些代码的时候,因为对我们来说,promise 所处理的就是时间复杂度上的问题。
promise 以时间无关的方式来作为一个单一的值。此外,获取 promise 的返回值是异步的,但却是通过同步的方法来赋值。或者说, promise 给
=操作符扩展随时间动态赋值的功能,通过可靠的(时间无关)方式。
接下来我们将探索如何以相同的方式,在时间上异步地拓展本书之前同步的函数式编程操作。
积极的 vs 惰性的
积极的和惰性的在计算机科学的领域并不是表扬或者批评的意思,而是描述一个操作是否立即执行或者是延时执行。我们在本例子中看到的函数式编程操作可以被称为积极的,因为它们同步(即时)地操作着离散的即时值或值的列表/结构上的值。
回忆下:
var a = [1,2,3] var b = a.map( v => v * 2 ); b; // [2,4,6]
这里
a到
b的映射就是积极的,因为它在执行的那一刻映射了数组
a里的所有的值,然后生成了一个新的数组
b。即使之后你去修改
a,比如说添加一个新的值到数组的最后一位,也不会影响到
b的内容。这就是积极的函数式编程。
但是如果是一个惰性的函数式编程操作呢?思考如下情况:
var a = []; var b = mapLazy( a, v => v * 2 ); a.push( 1 ); a[0]; // 1 b[0]; // 2 a.push( 2 ); a[1]; // 2 b[1]; // 4
我们可以想象下
mapLazy(..)本质上
“监听” 了数组
a,只要一个新的值添加到数组的末端(使用
push(..)),它都会运行映射函数
v => v * 2并把改变后的值添加到数组
b里。
注意:
mapLazy(..)的实现没有被写出来,是因为它是虚构的方法,是不存在的。如果要实现
a和
b之间的惰性的操作,那么简单的数组就需要变得更加聪明。
考虑下把
a和
b关联到一起的好处,无论何时何地,你添加一个值进
a里,它都将改变且映射到
b里。它比同为声明式函数式编程的
map(..)更强大,但现在它可以随时地变化,进行映射时你不用知道
a里面所有的值。
相关文章推荐
- 翻译连载 | 第 10 章:异步的函数式(下)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 | 第 10 章:异步的函数式(上)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 | 第 10 章:异步的函数式(下)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 | 第 10 章:异步的函数式(上)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 | JavaScript 轻量级函数式编程-第2章:函数基础 |《你不知道的JS》姊妹篇
- 翻译连载 |《你不知道的JS》姊妹篇 | JavaScript 轻量级函数式编程-引言&前言
- 翻译连载 | JavaScript轻量级函数式编程-第7章: 闭包vs对象 |《你不知道的JS》姊妹篇
- 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 第 5 章:减少副作用
- 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 第 4 章:组合函数
- 翻译连载 | 附录 B: 谦虚的 Monad-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 第 3 章:管理函数的输入
- 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 第 2 章:函数基础
- 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 第 1 章:为什么使用函数式编程?
- 翻译连载 | 第 9 章:递归(上)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 | 第 9 章:递归(下)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 | 第 11 章:融会贯通 -《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
- 翻译连载 | JavaScript轻量级函数式编程-第4章:组合函数 |《你不知道的JS》姊妹篇
- 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 引言&前言
- 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 第 8 章:列表操作
- 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 第 7 章: 闭包 vs 对象