翻译连载 | 第 10 章:异步的函数式(上)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇

关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTML 最坚实的梁柱;分享,是 CSS 里最闪耀的一瞥;总结,是 JavaScript 中最严谨的逻辑。通过捶打磨练,成就了本书的中文版。本书包含了函数式编程之精髓,但愿能够帮助你们在学习函数式编程的道路上走的更顺畅。比心。前端

译者团队(排名不分前后):阿希bluekenbrucechamcfanlifedailkyoko-dfl3velilinsLittlePineappleMatildaJin冬青pobusamaCherry萝卜vavd317vivaxy萌萌zhouyaogit

第 10 章:异步的函数式(上)

阅读到这里,你已经学习了我所说的全部轻量级函数式编程的基础概念,在本章节中,咱们将把这些概念应有到不一样的情景当中,但绝对不会有新的知识点。github

到目前为止,咱们所说的一切都是同步的,意味着咱们调用函数,传入参数后立刻就会获得返回值。大部分的状况下是没问题的,但这几乎知足不了现有的 JS 应用。为了能在当前的 JS 环境里使用上函数式编程,咱们须要去了解异步的函数式编程。编程

本章的目的是拓展咱们对用函数式编程管理数据的思惟,以便以后咱们在更多的业务上应用。数组

时间状态

在你全部的应用里,最复杂的状态就是时间。当你操做的数据状态改变过程比较直观的时候,是很容易管理的。可是,若是状态随着时间由于响应事件而隐晦的变化,管理这些状态的难度将会成几何级增加。promise

咱们在本文中介绍的函数式编程可让代码变得更可读,从而加强了可靠性和可预见性。可是当你添加异步操做到你的项目里的时候,这些优点将会大打折扣。app

必须明确的一点是:并非说一些操做不能用同步来完成,或者触发异步行为很容易。协调那些可能会改变应用程序的状态的响应,这须要大量额外的工做。异步

因此,做为做者的你最好付出一些努力,或者只是留给阅读你代码的人一个难题,去弄清楚若是 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]
复制代码

这里 ab 的映射就是积极的,由于它在执行的那一刻映射了数组 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(..) 的实现没有被写出来,是由于它是虚构的方法,是不存在的。若是要实现 ab 之间的惰性的操做,那么简单的数组就须要变得更加聪明。

考虑下把 ab 关联到一块儿的好处,不管什么时候何地,你添加一个值进 a 里,它都将改变且映射到 b 里。它比同为声明式函数式编程的 map(..) 更强大,但如今它能够随时地变化,进行映射时你不用知道 a 里面全部的值。

** 【上一章】翻译连载 | 第 9 章:递归(下)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇 **

iKcamp原创新书《移动Web前端高效开发实战》已在亚马逊、京东、当当开售。

iKcamp官网:www.ikcamp.com


2019年,iKcamp原创新书《Koa与Node.js开发实战》已在京东、天猫、亚马逊、当当开售啦!

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息