函數(shù)式編程范式
為什么學(xué)習(xí)函數(shù)式編程
- 函數(shù)式編程是隨著react的流行受到了越來越多的關(guān)注
- vue 3也開始擁抱函數(shù)式編程
- 函數(shù)式編程可以拋棄this
- 打包過程中可以更好的利用tree shaking過濾無用代碼
- 方便測試和并行處理
當(dāng)前也有很多庫可以幫助我們進(jìn)行函數(shù)式開發(fā)紊馏,eg:lodash奉件,underscore,ramda
函數(shù)式編程概念
函數(shù)式編程(Functional Programming,F(xiàn)P)悦荒,F(xiàn)P是編程范式之一。
編程范式還有:
- 面向過程編程: 按照步驟县好,一步一步來實(shí)現(xiàn)构哺。
- 面向?qū)ο缶幊蹋喊熏F(xiàn)實(shí)世界中的事物抽象成程序世界中的類和對(duì)象,通過封裝熬芜,繼承和多態(tài)來演示事物事件的聯(lián)系
- 函數(shù)式編程:把現(xiàn)實(shí)世界的事物和事物之間的聯(lián)系抽象到程序世界莲镣。
程序的本質(zhì):根據(jù)輸入,經(jīng)過運(yùn)算涎拉,獲得輸出瑞侮。執(zhí)行這個(gè)過程的就是函數(shù)。而函數(shù)式編程就是對(duì)這個(gè)運(yùn)算過程進(jìn)行抽象曼库,但是面向?qū)ο缶幊淌菍?duì)事物進(jìn)行抽象区岗。
函數(shù)式編程中的函數(shù)指的不是程序中的函數(shù)(方法),指的是數(shù)學(xué)中的函數(shù)毁枯,就是一個(gè)映射關(guān)系慈缔,eg: y = sin(x)铺罢。此時(shí)薪捍,相同的輸入始終要得到相同的輸出(純函數(shù))。
//非函數(shù)式
let num1 = 1
let add = num1 + 1
//函數(shù)式
function add1(num){
return num + 1
}
let add = add1(2)
函數(shù)是一等公民
- 函數(shù)可以存儲(chǔ)在變量中
- 函數(shù)可以作為參數(shù)
- 函數(shù)可以作為返回值
eg:
//函數(shù)表達(dá)式就是把函數(shù)賦值給變量
let fun = function (){
console.log('hello')
}
//改造成賦值給變量
const obj = {
show(params){ return otherObj.show(params) }
}
//修改后
const obj = {
show: otherObj.show
}
高階函數(shù)(Higher-order function)
高階函數(shù)就是把函數(shù)作為參數(shù)傳遞給另一個(gè)函數(shù)登疗,或者把函數(shù)作為另一個(gè)函數(shù)的返回結(jié)果赂韵。
// 利用高階函數(shù)-將函數(shù)作為參數(shù)寫一個(gè)forEach函數(shù)
function forEach(arr, fn) {
for (let i = 0; i < arr.length; i++) {
fn(arr[i], i)
}
}
const arr = [1, 2, 3, 4, 5]
forEach(arr, (item, index) => {
console.log(item, index)
})
// 利用函數(shù)可以作為返回值娱节,實(shí)現(xiàn)一個(gè)once函數(shù)-只執(zhí)行一次
function once(fn) {
let mark = false
return function () {
if (!mark) {
mark = true
fn.apply(this, arguments)
}
}
}
let pay = once((money) => {
console.log(`${money} RMB`)
})
pay(100)
pay(100)
pay(100)
pay(100)
高階函數(shù)的意義:
- 抽象通用的問題
- 屏蔽細(xì)節(jié),只關(guān)注我們的目標(biāo)
常用的高階函數(shù):
forEach, filter, map, every, some...
閉包
在把函數(shù)當(dāng)作返回值返回時(shí)祭示,內(nèi)部函數(shù)可以訪問外部函數(shù)中的變量肄满,就形成了閉包。
function outter() {
let num = 0
return function inner() {
console.log(num++)
}
}
let o = outter()
o()
o()
o()
以上例子,如果outter內(nèi)部沒有閉包稠歉,outter調(diào)用完畢后掰担,outter從執(zhí)行棧上移除,同時(shí)它內(nèi)部的變量num也會(huì)從內(nèi)存中移除怒炸,但是內(nèi)部有了inner引用了變量num带饱,outter從執(zhí)行棧移除后,num因?yàn)檫€被inner引用阅羹,不會(huì)被移除勺疼,即-堆上的作用域成員因?yàn)楸煌獠恳貌荒茚尫拧?/p>
純函數(shù)
純函數(shù):相同的輸入永遠(yuǎn)會(huì)得到相同的輸出,而且沒有任何可觀察的副作用捏鱼。
lodash是要給純函數(shù)的功能庫执庐,提供了對(duì)數(shù)組,數(shù)字穷躁,對(duì)象耕肩,字符串,函數(shù)等操作的一些方法问潭。
eg:數(shù)組的slice方法是純函數(shù)猿诸,splice是不純的函數(shù)。
- slice:返回?cái)?shù)組中的指定部分狡忙,不會(huì)改變?cè)瓟?shù)組梳虽。
- splice:對(duì)數(shù)組進(jìn)行操作返回該數(shù)組,會(huì)改變?cè)瓟?shù)組灾茁。
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
// slice 沒有修改原數(shù)組窜觉,每次返回結(jié)果一樣,是純函數(shù)
console.log(arr.slice(1, 3)) //[ 2, 3 ]
console.log(arr.slice(1, 3)) //[ 2, 3 ]
console.log(arr.slice(1, 3)) //[ 2, 3 ]
// splice 修改了原數(shù)組北专,每次返回結(jié)果不一樣禀挫,是不純的函數(shù)
console.log(arr.splice(1, 3)) //[ 2, 3, 4 ]
console.log(arr.splice(1, 3)) //[ 5, 6, 7 ]
console.log(arr.splice(1, 3)) //[ 8, 9 ]
純函數(shù)的好處:
- 可緩存。因?yàn)榧兒瘮?shù)對(duì)相同的輸入始終有相同的結(jié)果拓颓,所以可以把純函數(shù)的結(jié)果緩存起來语婴。
// 自己實(shí)現(xiàn)一個(gè)類似lodash中的 memoize
function getArea(r) {
console.log(r)
return Math.PI * r * r
}
function memoize(f) {
let cache = {}
return function () {
let key = JSON.stringify(arguments)
cache[key] = cache[key] || f.apply(f, arguments)
return cache[key]
}
}
let getAreaWithMemory = memoize(getArea)
console.log(getAreaWithMemory(4)) // 4 50.26548245743669
console.log(getAreaWithMemory(4)) // 50.26548245743669
console.log(getAreaWithMemory(5)) // 5 78.53981633974483
- 可測試
- 并行處理。在多線程環(huán)境(eg:Web Worker)下并行操作共享的內(nèi)存數(shù)據(jù)很可能出現(xiàn)意外驶睦,純函數(shù)不需要訪問共享的內(nèi)存數(shù)據(jù)砰左,所以在并行環(huán)境下可以任意運(yùn)行純函數(shù)。
純函數(shù)的副作用:
eg:
// 不純
let mini = 18
function checkAge(age) {
return age >= mini
}
// 純的场航,有硬編碼
function checkAge(age) {
let mini = 18
return age >= mini
}
如果函數(shù)依賴于外部狀態(tài)缠导,就無法保證輸出相同,就會(huì)帶來副作用溉痢,變得不純僻造。
在函數(shù)內(nèi)部定義一個(gè)變量的值憋他,又會(huì)出現(xiàn)硬編碼。不過這個(gè)可以通過柯里化解決嫡意。
副作用不可完全禁止举瑰,盡可能控制他們?cè)诳煽胤秶鷥?nèi)發(fā)生。
柯里化
使用柯里化可以解決硬編碼的問題蔬螟。
柯里化: 當(dāng)一個(gè)函數(shù)有多個(gè)參數(shù)的時(shí)候,先傳遞一部分參數(shù)調(diào)用它汽畴,這部分參數(shù)以后永遠(yuǎn)不變旧巾,然后返回一個(gè)新的函數(shù)接受剩余的參數(shù),返回結(jié)果忍些。
eg:
let checkAge = min => (age => age >= min)
let checkAge18 = checkAge(18)
let checkAge20 = checkAge(20)
console.log(checkAge18(20))
console.log(checkAge20(24))
lodash中的柯里化函數(shù)
const _ = require('lodash')
function getSum(a, b, c) {
return a + b + c
}
const curried = _.curry(getSum)
console.log(curried(1, 2, 3)) //6
console.log(curried(1)(2, 3)) //6
console.log(curried(1)(2)(3)) //6
lodash中的curry方法可以將一個(gè)多元函數(shù)鲁猩,轉(zhuǎn)換為任意多元函數(shù)。
自己實(shí)現(xiàn)一個(gè)lodash.curry
function curry(fn){
return function a(...args){
if(args.length < fn.length){
return function(){
return a(...args.concat(Array.from(arguments)))
}
}
return fn(...args)
}
}
function getSum(a, b, c) {
return a + b + c
}
const curried = curry(getSum)
console.log(curried(1, 2, 3))
console.log(curried(1)(2, 3))
console.log(curried(1)(2)(3))
總結(jié)
- 柯里化可以返回一個(gè)新函數(shù)罢坝,這個(gè)新函數(shù)已經(jīng)記住了某些固定參數(shù)
- 這是一種對(duì)函數(shù)參數(shù)的‘緩存’
- 讓函數(shù)的粒度更小廓握,更靈活
- 可以把多元函數(shù)轉(zhuǎn)換成一元函數(shù),組合使用函數(shù)產(chǎn)生強(qiáng)大的功能
函數(shù)組合
純函數(shù)和柯里化很容易寫出洋蔥代碼嘁酿,eg:_.toUpper(_.first(_.reverse(arr)))
隙券,而以函數(shù)組合的形式 fn = compose(f1, f2, f3) 把中間過程的函數(shù)合并成一個(gè)函數(shù),看起來更簡潔闹司。函數(shù)組合默認(rèn)是從右到左執(zhí)行娱仔,所以執(zhí)行順序是 f3, f2, f1。
// 函數(shù)組合演示
// 洋蔥代碼并沒有被省略游桩,而是被封裝起來了
function compose(f, g) {
return function (value) {
return f(g(value))
}
}
// 實(shí)現(xiàn)將數(shù)組先反轉(zhuǎn)在取第一個(gè)
function reverse(arr) {
return arr.reverse()
}
function first(arr) {
return arr[0]
}
const last = compose(first, reverse)
console.log(last([1, 2, 3, 4, 5])) //5
lodash中的組合函數(shù)
- flow() 從左到右執(zhí)行
- flowRight() 從右到左執(zhí)行牲迫,使用的更多
// 使用lodash 中的函數(shù)組合的方法: _.flowRight()
const _ = require('lodash')
// 實(shí)現(xiàn)先翻轉(zhuǎn),再去第一個(gè)借卧,再大寫
const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpper = s => s.toUpperCase()
const f = _.flowRight(toUpper, first, reverse)
console.log(f(['one', 'two', 'three']))
自己實(shí)現(xiàn)flowRight
function compose(...args) {
return function (value) {
return args.reverse().reduce((pre, pro) => {
return pro(pre)
}, value)
}
}
// 改為箭頭函數(shù)
const compose = (...args) => value => args.reverse().reduce((pre, pro) => pro(pre), value)
const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpper = s => s.toUpperCase()
const f = compose(toUpper, first, reverse)
console.log(f(['one', 'two', 'three'])) // THREE
函數(shù)組合-結(jié)合律
函數(shù)組合要滿足結(jié)合律盹憎,即:
let associative = comose(compose(f,g),h) == compose(f,compose(g,h)) //true
函數(shù)組合-調(diào)試
函數(shù)組合的方式,如果最終結(jié)果不是預(yù)期铐刘,很難找出是哪一步出了錯(cuò)陪每,eg:
// NEVER SAY DIE --> never-say-die
//將左側(cè)字符串轉(zhuǎn)換成右側(cè)的字符串,要經(jīng)過分割滨达,變小寫奶稠,再用-隔開
const log = _.curry((tag, v) => {
console.log(tag, v)
return v
})
//為了滿足函數(shù)組合參數(shù)都是一個(gè),將split和join柯里化
// _.toLower()
// _.split()
const split = _.curry((sep, str) => _.split(str, sep))
// _.join()
const join = _.curry((sep, arr) => _.join(arr, sep))
const f = _.flowRight(join('-'), _.toLower, split(' '))
console.log(f('NEVER SAY DIE')) //n-e-v-e-r-,-s-a-y-,-d-i-e
打印后發(fā)現(xiàn)結(jié)果和預(yù)期不一樣捡遍,但是不好定位錯(cuò)誤位置锌订,我們可以給函數(shù)組合的每個(gè)函數(shù)中間加個(gè)log來打印每次結(jié)果是否正確。
const log = (v) => {
console.log(v)
return v
}
const f = _.flowRight(join('-'), _.toLower, log, split(' ')) //[ 'NEVER', 'SAY', 'DIE' ]
const f = _.flowRight(join('-'), log, _.toLower, split(' ')) //never,say,die
可以看到画株,在_.toLower后辆飘,數(shù)組變成了字符串格式啦辐,和預(yù)期不一樣。但是這樣打log蜈项,如果同時(shí)放入多個(gè)log芹关,分不清打印的是哪一步,可以通過改造:
// tag 用來標(biāo)注位置紧卒,v是結(jié)果侥衬,用_.curry柯里化
const log = _.curry((tag, v) => {
console.log(tag, v)
return v
})
const f = _.flowRight(join('-'), log('lower 后'), _.toLower, log('split 后'), split(' '))
// split 后 [ 'NEVER', 'SAY', 'DIE' ]
// lower 后 never,say,die
lodash-fp模塊
lodash中的fp模塊提供了實(shí)用的對(duì)函數(shù)式編程友好的方法,并且函數(shù)優(yōu)先跑芳,數(shù)據(jù)滯后轴总,自動(dòng)柯里化。
// lodash 模塊, 都是數(shù)據(jù)優(yōu)先博个,函數(shù)滯后
_.map(['a', 'b', 'c'], _.toUpper) // ['A','B','C']
_.map(['a', 'b', 'c']) // ['a', 'b', 'c']
_.split('Hello World', ' ')
// lodash/fp 模塊, 都是函數(shù)優(yōu)先怀樟,數(shù)組滯后
const fp = require('lodash/fp')
fp.map(fp.toUpper, ['a', 'b', 'c'])
fp.map(fp.toUpper)(['a', 'b', 'c'])
fp.split(' ', 'Hello World')
fp.split(' ')('Hello World')
將前面的例子做修改
const f = fp.flowRight(fp.join('-'), fp.split(' '), fp.toLower)
console.log(f('NEVER SAY DIE')) // never-say-die
lodash 和 lodash/fp 模塊中的map方法的區(qū)別
首先執(zhí)行console.log(_.map(['23','8','10'], parseInt)) // [ 23, NaN, 2 ]
,想將數(shù)組中的字符串轉(zhuǎn)為數(shù)字盆佣,但是最終拿到的結(jié)果和預(yù)期不一樣往堡,讓我們看一下_.map的解釋:
函數(shù)接收到的是三個(gè)參數(shù),value,index,array共耍,所以此時(shí)parseInt是這樣執(zhí)行的:
parseInt('23', 0, array)
parseInt('8', 1, array)
parseInt('10', 2, array)
parseInt的第二個(gè)參數(shù)是幾進(jìn)制虑灰,所以此時(shí)結(jié)果不符合預(yù)期。但是看一下fp.map方法:
可以看到fp.map方法中的函數(shù)只接受一個(gè)參數(shù),
console.log(fp.map(parseInt, ['23', '8', '10'])) //[ 23, 8, 10 ]
,parseInt接受一個(gè)參數(shù)征堪,就不會(huì)出現(xiàn)剛才的問題瘩缆。
Point Free
Point Free是一種編程的風(fēng)格,具體形式就是函數(shù)組合佃蚜。
- 不需要指明處理的數(shù)據(jù)
- 只需要合成運(yùn)算規(guī)則
- 需要定義一些輔助的基本運(yùn)算函數(shù)
eg:const f = fp.flowRight(fp.join('-'), fp.split(' '), fp.toLower)
這個(gè)例子中將運(yùn)算過程合成庸娱,沒有指明要處理的數(shù)據(jù)。
// Hello Word => hello_world
// fp 中的方法都是柯里化的
// fp.replace可以接受3個(gè)參數(shù)谐算,1 匹配的正則熟尉, 2 替換成什么 3 要修改的字符串
// fp.replace是柯里化所以接收兩個(gè)參數(shù)可以返回一個(gè)函數(shù),這個(gè)函數(shù)可以接受一個(gè)字符串
const f = fp.flowRight(fp.replace(/\s+/g, '_'), fp.toLower)
console.log(f('Hello Word')) // hello_word
案例:
// 把一個(gè)字符串中的首字母提取洲脂,轉(zhuǎn)換成大寫斤儿,使用. 作為分隔符
// world wild web ==> W. W. W
const f = fp.flowRight(fp.toUpper, fp.join('. '), fp.map(fp.first), fp.split(' '), fp.replace(/\s+/g, ' '))
// const f = fp.flow(fp.replace(/\s+/g, ' '), fp.split(' '), fp.map(fp.first), fp.join('. '), fp.toUpper)
console.log(f('world wild web')) // W. W. W
Functor(函子)
- 容器:包含值和值的變形關(guān)系(函數(shù))
- 函子:是一個(gè)特殊的容器,通過一個(gè)普通的對(duì)象來實(shí)現(xiàn)恐锦,該對(duì)象具有map方法往果,map方法可以運(yùn)行一個(gè)函數(shù)對(duì)值進(jìn)行處理。
// Functor 函子
class Container {
constructor(value) {
// 維護(hù)一個(gè)不對(duì)外公布的靜態(tài)變量
this._value = value
}
// map方法通過fn處理值一铅,并通過新的函子返回
map(fn) {
return new Container(fn(this._value))
}
}
// map方法返回的是新的函子陕贮,所以還可以調(diào)用map方法
const lr = new Container(5)
.map(v => v + 3)
.map(v => v * 2)
console.log(lr) // Container { _value: 16 }
上邊的new Container可以通過封裝:
class Container {
static of(value) {
return new Container(value)
}
constructor(value) {
// 維護(hù)一個(gè)不對(duì)外公布的靜態(tài)變量
this._value = value
}
// map方法通過fn處理值,并通過新的函子返回
map(fn) {
return Container.of(fn(this._value))
}
}
// map方法返回的是新的函子潘飘,所以還可以調(diào)用map方法
const lr = Container.of(5)
.map(v => v + 3)
.map(v => v * 2)
console.log(lr) // Container { _value: 16 }
總結(jié):
- 函數(shù)式編程的運(yùn)算不直接操作值肮之,而是由函子完成
- 函子就是一個(gè)實(shí)現(xiàn)了map契約的對(duì)象
- 我們可以把函子想象成一個(gè)盒子掉缺,這個(gè)盒子里封裝了一個(gè)值
- 想要處理盒子中的值,我們需要給盒子的map方法傳遞一個(gè)處理值的函數(shù)(純函數(shù))戈擒,由這個(gè)函數(shù)來對(duì)值進(jìn)行處理
- 最終map方法返回一個(gè)包含新值的盒子(函子)
MayBe函子
- 對(duì)編程過程中可能遇到的錯(cuò)誤做相應(yīng)的處理
- 對(duì)外部的空值情況做處理(控制副作用在允許的范圍)
class MayBe {
static of(value) {
return new MayBe(value)
}
constructor(value) {
this._value = value
}
// 通過 isNothing 判斷是否為空值
map(fn) {
return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
}
isNothing() {
return this._value === null || this._value === undefined
}
}
let r = MayBe.of(null)
.map(x => x.toUpperCase())
console.log(r) // MayBe { _value: null }
但是眶明,以下這種情況,不會(huì)出錯(cuò)筐高,但是什么時(shí)候出現(xiàn)null不知道
let r = MayBe.of('hello world')
.map(x => x.toUpperCase())
.map(x => null)
.map(x=> x.toUpperCase())
console.log(r) // MayBe { _value: null }
可以通過Either函子來解決
Either函子
Either函子可以用來處理異常搜囱。
class Left {
static of(value) {
return new Left(value)
}
constructor(value) {
this._value = value
}
map(fn) {
return this
}
}
class Right {
static of(value) {
return new Right(value)
}
constructor(value) {
this._value = value
}
map(fn) {
return Right.of(fn(this._value))
}
}
function parseJson(str) {
try {
return Right.of(JSON.parse(str))
} catch (e) {
return Left.of({ error: e.message })
}
}
let r1 = parseJson('{name:zs}')
console.log(r1) // Left { _value: { error: 'Unexpected token n in JSON at position 1' } }
let r2 = parseJson('{"name":"zs"}')
.map(x => x.name.toUpperCase())
console.log(r2) // Right { _value: 'ZS' }
使用Left來處理異常,Right來做正確的操作凯傲。
IO函子
- IO是input犬辰,output的意思。IO函子中的_value 是一個(gè)函數(shù)冰单,整理把函數(shù)作為值來處理。
- IO函子可以把不純的動(dòng)作存儲(chǔ)到_value中灸促,延遲執(zhí)行诫欠,包裝它的操作是純的。
- 把不純的操作交給調(diào)用者來處理浴栽。
// IO 函子
class IO {
static of(value) {
// this._value 就是這里的function
return new IO(function () {
return value
})
}
constructor(fn) {
this._value = fn
}
map(fn) {
return new IO(fp.flowRight(fn, this._value))
}
}
// process - node 進(jìn)程
let r = IO.of(process).map(p => p.execPath)
console.log(r) // IO { _value: [Function (anonymous)] }
console.log(r._value()) // D:\node.js\node.exe
IO函子的問題
const fs = require('fs')
let readFile = function (filename){
return new IO(function(){
return fs.readFileSync(filename, 'utf-8')
})
}
let print = function(x){
return new IO(function(){
console.log(x)
return x
})
}
let cat = fp.flowRight(print, readFile)
// IO(IO(x))
// let r = cat('../../../package.json')._value() // IO { _value: [Function (anonymous)] }
let r = cat('../../../package.json')._value()._value()
console.log(r)
如果出現(xiàn)函子嵌套IO(IO(x))荒叼,需要拿多層_value才能拿到值。
Monad函子
- Monad函子是可以變扁的Pointed函子IO(IO(x))
- 一個(gè)函子如果具有join和of兩個(gè)方法并遵守一些定律就是一個(gè)Monad
class IO {
static of(value) {
return new IO(function () {
return value
})
}
constructor(fn) {
this._value = fn
}
map(fn) {
return new IO(fp.flowRight(fn, this._value))
}
join() {
return this._value()
}
flatMap(fn) {
return this.map(fn).join()
}
}
const fs = require('fs')
let readFile = function (filename) {
return new IO(function () {
return fs.readFileSync(filename, 'utf-8')
})
}
let print = function (x) {
return new IO(function () {
console.log(x)
return x
})
}
let r = readFile('../../../package.json')
.map(fp.toUpper)
.flatMap(print)
.join()
console.log(r)
Pointed函子
- Pointed函子是時(shí)下你了of靜態(tài)方法的函子
- of方法是為了避免使用new來創(chuàng)建對(duì)象典鸡,更深層的含義是of方法用來把值放到上下文Context被廓。
Task函子
Folktale
folktale是一個(gè)標(biāo)準(zhǔn)的函數(shù)是編程庫,但是它和lodash萝玷,ramda不同的是嫁乘,他沒有提供很多功能函數(shù),而是提供了一些函數(shù)是處理的操作球碉,eg:compose蜓斧, curry等,一些函子Task睁冬,Either挎春,MayBe等。
基本使用:
const { compose, curry } = require('folktale/core/lambda')
const { toUpper, first } = require('lodash/fp')
let f = curry(2, (x, y) => {
return x + y
})
console.log(f(1, 2)) // 3
console.log(f(1)(2))// 3
let n = compose(toUpper, first)
console.log(n(['one', 'two'])) // ONE
folktale 2.x 中的Task和 1.0中的Task區(qū)別很大豆拨,1.0用法接近前邊的例子直奋,下邊一2.3.2來演示Task的使用:
Folktale中Task函子的使用
// Task 處理異步任務(wù)
const fs = require('fs')
const { task } = require('folktale/concurrency/task')
const { split, find } = require('lodash/fp')
function readFile(filename) {
return task(resolver =>
{
fs.readFile(filename, 'utf-8', (err, data) => {
if (err) resolver.reject(er)
resolver.resolve(data)
})
})
}
readFile('../../../package.json')
.map(split('\n'))
.map(find(x => x.includes('version')))
.run()
.listen({
onRejected: err => {
console.log(err)
},
onResolved: value => {
console.log(value)
}
})