柯里化

js 中函数作为一等公民,函数执行中既能够作为函数的参数也能够作为函数的返回值,而这类执行函数叫做高阶函数,利用高阶函数的个性很容易就能够实现柯里化(柯里化(Currying)是把承受多个参数的函数变换成承受一个繁多参数(最后函数的第一个参数)的函数,并且返回承受余下的参数且返回后果的新函数的技术),依据百科的了解大略就是上面的例子。

function add(x, y) {  return x + y}function curryind_add(x) {  return function(y) {    return x + y  }}const curried_add = curryind_add(1)curried_add(2) // 3curried_add(3) // 4

从例子能够看出,柯里化有避免参数反复的作用,而且具备提早执行的特色。上面利用柯里化的特色看看在异步编程的利用,在开始时先简略介绍一下 async/await 的原理。

Generator 和 promise

async/await 语法其实是 Generator 和 promise 的语法糖。

Generator 函数执行时会返回一个生成器对象,该对象是一个非凡的迭代器对象,而且自身也是一个可迭代对象(迭代器对象和可迭代对象),上面说说其特殊性。

function *generator() {  const value = yield 1  console.log(value)  try { // 捕捉谬误    yield 2  } catch(e) {    console.log(e)  }}const iterator = generator();iterator === iterator[Symbol.iterator](); // true 本身部署了可迭代接口,该接口返回本身[...iterator] // [1, 2]const iterator2 = generator(); // 迭代器对象是一次耗费的,须要从新起一个迭代器iterator2.next() // {value: 1, done: false}iterator2.next(`value 的值`) // 利用 next 向生成器函数发送数据iterator2.throw(new Error('error')) // 利用 throw 向生成器函数抛出谬误

从下面的例子能够看出,生成器对象,在迭代的过程中是能够和生成器函数进行通信的,如果用在 promise 中,只有把 promise 在状态扭转后执行的回调后果回传到生成器函数内就能够实现相似async/await 的成果。而迭代过程,能够实现一个执行器函数进行迭代,这个函数就相似于执行 async。

function createDelayPromise (time) {  return new Promise((resolve) => {    setTimeout(() => {      resolve(time)    }, time)  })} function *createIterator() {  const time1 = yield createDelayPromise(1000)  console.log(time1)  const time2 = yield createDelayPromise(2000)  console.log(time2)  const time3 = yield createDelayPromise(3000)  console.log(time3)  return time1 + time2 + time3}function run(createIterator) {  const iterator = createIterator()  let result = iterator.next()  let r    const p = new Promise(resolve => {    r = resolve  })  function next() {    if (result.done) {      r(result.value)    } else {      Promise.resolve(result.value).then(value => {        result = iterator.next(value)        next()      }).catch((err) => {        result = iterator.throw(err)        next()      })    }  }  next()  return p}run(createIterator).then((value) => {  console.log(value)})

下面应用 createDelayPromise 封装了一个 promise 类型的异步工作,而后应用 next 办法去迭代这个迭代器对象。期待对应的异步工作有后果后就通过生成器对象的 next 和 throw 办法把后果回传到生成器函数中。
下面是应用 promise 配合 generator 能够让异步代码以相似同步的模式写在生成器函数中,同样地对应柯里化后的函数,同样具备提早执行的成果,也能够通过配合 generator 实现相似的成果。

Generator 和 柯里化

除了一些接口是实现了 promise 化外,有很多比拟久的接口仍然是应用 callback 类型的接口,像 node 中的很多接口会把 callback 参数放在参数列表的开端,并把 err 放在回调执行的第一个地位,相似这样

function createDelayCurry (time, callback) {  setTimeout(() => {    callback(null, time)  }, time)} 

上面通过实现不同以上的 run 办法,联合柯里化实现以上的成果,这次 yield 前面不再是 promise 化后的对象,而是柯里化后的函数
在开始之前先实现一个通用的 curry 函数

const curry = (fn, ...args) => fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);

这个 curry 函数的作用是等初始存入的函数的参数收集够就执行,如果不够就持续收集,上面会在迭代中补上回调参数,这样异步工作才能够执行。

const curry = (fn, ...args) => fn.length <= args.length ? fn(...args) : curry.bind(null, fn, ...args);function createDelayCurry (time, callback) {  setTimeout(() => {    callback(null, time)  }, time)} const curringDelay = curry(createDelayCurry)function *createIterator() {  const time1 = yield curringDelay(1000)  console.log(time1)  const time2 = yield curringDelay(2000)  console.log(time2)  const time3 = yield curringDelay(3000)  console.log(time3)  return time1 + time2 + time3}function run(createIterator, callback) {  const iterator = createIterator()  let result = iterator.next()  function next() {    if (result.done) {      callback(null, result.value)    } else {      result.value((err, value) => { // 补上最初 callback 参数启动工作        if (err) {          result = iterator.throw(err)          next()        } else {          result = iterator.next(value)          next()        }      })    }  }  next()}run(createIterator, (err, value) => {  console.log(err, value)})

执行成果和 promise 版是差不多一样的。

并行

下面是串行的版本,上面看看并行的实现
先看 promise 并行个别写法

function *createIteratorParallel() {  const times = yield Promise.all([createDelayPromise(1000), createDelayPromise(2000), createDelayPromise(3000)])  return times}

相应的也实现一个 all 办法

function curryAll(curries, callback) {  let len = curries.length  let result = []  let count = 0  curries.forEach((curried, index) => {    curried((err, value) => {      if (err) {        callback(err)        return      } else {        count++        result[index] = value        if (count == len) {          callback(null, result)        }      }    })  })}const curriedAll = curry(curryAll)function *createIteratorParallel() {  const times = yield curriedAll([curringDelay(1000), curringDelay(2000), curringDelay(3000)])  return times}

yield 前面仍然是柯里化后的函数

es 新语法

下面的计划要求异步工作的回调须要在参数的开端地位,如果回调不在开端,那么就须要批改 curry 函数的实现形式,然而有没有可能不必批改其实现形式,甚至不必写 curry 呢?
curry 作为函数式编程的根本单元,最新的 es 标准实验性地从语法的角度提供了反对。

function add(x, y) { return x + y; }const addOne = add(1, ?); // apply from the leftaddOne(2); // 3const addTen = add(?, 10); // apply from the rightaddTen(2); // 12

以上的 addOne addTen 就是一个柯里化后的函数,这个语法大部分浏览器都没有反对,如果要应用也很简略,应用一个 babel 插件就能够了 babel --plugins @babel/plugin-proposal-partial-application script.js
如果应用了下面的语法,能够不必引入 curry 函数

function *createIterator() {  const time1 = yield createDelayCurry(1000, ?)  console.log(time1)  const time2 = yield createDelayCurry(2000, ?)  console.log(time2)  const time3 = yield createDelayCurry(3000, ?)  console.log(time3)  return time1 + time2 + time3}

如果 callback 在后面,那么就把问号放在后面,实际上能够放在任何参数地位上,次要看具体的接口要求。

总结

柯里化的利用十分宽泛,下面只是举了一个简略的例子,通过这个例子,也理解了 async/await 根本实现原理,尽管 async/await 曾经被广泛支持,promise 也被宽泛应用,可能不再须要间接应用生成器函数了,但也不障碍去简略地理解一下。