翻译连载 | 第 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官网:http://www.ikcamp.com

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