上一篇我们讲了同步链式处理数据函子的概念。这一节,我们来讲异步。用到的概念很简单,不需要有函数式编程的基础。当然如果你看了那篇 《在你身边你左右 --函数式编程别烦恼》 会更容易理解。这一篇我们会完成一个Promise代码的编写。本文会从实现一个只有十几行代码能够解决异步链式调用问题的简单的Promise开始。然后逐渐完善增加功能。

  • 实现简单的异步Promise函子
  • 能够同时调用同一Promise函子
  • 增加reject回调函数
  • 增加Promise状态

本文代码在我的github

1 实现简单的Promise函子

我们先来回顾一下同步链式调用。

class Functor{constructor (value) {this.value = value ;}      map (fn) {return Functor.of(fn(this.value))}}
Functor.of = function (val) {return new Functor(val);
}Functor.of(100).map(add1).map(add1).map(minus10)// var  a = Functor.of(100);
// var  b = a.map(add1);
// var  c = b.map(add1);
// var  d = c.map(minus10);复制代码
  • 函子的核心就是每个functor都是一个新的对象
  • 通过map中传递进去的函数fn去处理数据
  • 用得到的值去生成新的函子

那么如果当a的值是异步产生的,我们该何如传入this.value值呢?

function executor(resolve){setTimeout(()=>{resolve(100)},500)
}
复制代码

我们模拟一下通过setTimeout500毫秒后拿到数据100。其实也很简单,我们可以传进去一个resolve回调函数去处理这个数据。

class Functor {constructor (executor) {let _this = this;this.value = undefined;function resolve(value){_this.value = value;}executor(resolve)}
}var a = new Functor(executor);复制代码
  • 我们讲executor传入并立即执行
  • 在resolve回调函数中我们能够拿到value值
  • 我们定义resolve回调函数讲value的值赋给this.value

这样我们就轻松的完成了a这个对象的赋值。那么我们怎么用方法去处理这个数据呢?

  • 显然在拿到回调函数值之后,我们应该能让map里的fn去继续处理数据
  • 处理完这个数据,我们交给下一个函数的resolve去继续处理
  • 所以我们定义了一个callback函数,
  • 在调用map时,将就包含fn处理数据,和执行下一个对象的resolve的函数赋值给它
  • 然后在自己的resolve拿到值之后,我们执行这个callback
class Functor {constructor (executor) {let _this = this;this.value = undefined;this.callback = null;function resolve(value){_this.value = value;_this.callback()}executor(resolve)} map (fn) {let  self = this;return new Functor((resolve) => {self.callback = function(){let data =  fn(self.value)   resolve(data)}})}
}
new Functor(executor).map(add1).map(add1)
复制代码

现在我们已经实现了异步的链式调用,我们来具体分析一下,都发生了什么。

  • (1)a = new Functor(executor)的时候,我们进行了初始化, executor(resolve)开始执行
  • (2)b =a.map(add1)的时,先进行了初始化 new Functor(),然后执行 executor(resolve)
  • (3)b中executor(resolve)执行结束,将一个函数赋值a中的callback

注意:这时map中this指向的是a函子,但是 new Functor((resolve) => {}中resolve是B的

  • (4)最后return 一个新的函子b
  • (5)c =b.map(add1)的时,同样,给b中的callback赋值
  • (6)然后返回一个新的函子c,此时没有map的调用,c中的callback就是null

我们再来分析一下异步结束之后,回调函数中的resolve是如何执行的。

  • (1)resolve 先_this.value = value;把a中的value进行修改
  • (2)在执行_this.callback(),先let data = fn(self.value) 计算出处理后的data
  • (3)调用b中的resolve函数继续处理
  • (4)b中也是,先给value赋值,然后处理数据
  • (5)再调用c中的resolve,并把处理好的数据传给他
  • (6)先给C中value赋值,然后再处理数据,最后调用callback时因为不是函数会报错,之后我们会解决

本节代码:promise1.js

嗯,这就是promise作为函子实现的处理异步操作的基本原理。它已经能够解决了简单的异步调用问题。虽然代码不多,但这是promise处理异步调用的核心。接下来我们会不断继续实现其他功能。

2 同时调用同一个Promise函子

如果我们像下面同时调用a这个函子。你会发现,它实际上只执行了c。

var a = new Functor(executor);
var b = a.map(add);
var c = a.map(minus);
复制代码

原因很简单,因为上面我们学过,b先给a的callback赋值,然后c又给a的callback赋值。所以把b给覆盖掉了就不会执行啦。解决这个问题很简单,我们只需要让callback变成一个数组就解决啦。

class MyPromise {constructor (executor) {let _this = this;this.value = undefined;this.callbacks = [];function resolve(value){_this.value = value;_this.callbacks.forEach(item => item())}executor(resolve)} then (fn) {return new MyPromise((resolve) => {this.callbacks.push (()=>{let data =  fn(this.value) console.log(data)         resolve(data)})})}
}var a = new MyPromise(executor);
var b = a.then(add).then(minus);
var c = a.then(minus);复制代码
  • 我们定义了callbacks数组,每次的调用a的then方法时。都将其存到callbacks数组中。
  • 当回调函数拿到值时,在resolve中遍历执行每个函数。
  • 如果callbacks是空,forEach就不会执行,这也解决了之前把错的问题
  • 然后我们进一步改了函子的名字(MyPromise),将map改成then
  • 简化了return中,let self = this;

3 增加reject回调函数

我们都知道,在异步调用的时候,我们往往不能拿到数据,返回一个错误的信息。这一小节,我们对错误进行处理。

function executor(resolve,reject){fs.readFile('./data.txt',(err, data)=>{if(err){ console.log(err)reject(err)}else {resolve(data)}})
}
复制代码
  • 我们现在用node异步读取一个文件
  • 成功执行 resolve(data),失败执行 reject(err)

现在我们定义出这个reject

class MyPromise {constructor (executor) {let _this = this;this.value = undefined;this.reason = undefined;this.onResolvedCallbacks = [];this.onRejectedCallbacks = [];function resolve(value){_this.value = value;_this.onResolvedCallbacks.forEach(item => item())}function reject(reason){_this.reason = reason;_this.onRejectedCallbacks.forEach(item => item());}executor(resolve, reject);} then (fn,fn2) {return new MyPromise((resolve,reject) => {this.onResolvedCallbacks.push (()=>{let data =  fn(this.value) console.log(data)         resolve(data)})this.onRejectedCallbacks.push (()=>{let reason =  fn2(this.reason) console.log(reason)         reject(reason)})})}
}
复制代码
  • 其实很简单,就是我们就是在executor多传递进去一个reject
  • 根据异步执行的结果去判断执行resolve,还是reject
  • 然后我们在MyPromise为reject定义出和resolve同样的方法
  • 然后我们在then的时候应该传进去两个参数,fn,fn2

本节代码:promise3.js

这时候将executor函数封装到asyncReadFile异步读取文件的函数

function asyncReadFile(url){return new MyPromise((resolve,reject) => {fs.readFile(url, (err, data) => {if(err){ console.log(err)reject(err)}else {resolve(data)}})})
}
var a = asyncReadFile('./data.txt');
a.then(add,mismanage).then(minus,mismanage);
复制代码

这就是我们平时封装异步Promise函数的过程。但这是过程有没有觉得在哪见过。如果之前executor中的'./data.txt'我们是通过参数传进去的那么这个过程不就是上一节我们提到的柯里化。

本节代码:promise4.js

我们再来总结一下上面的过程。

  • 我们先进行了初始化,去执行传进来的 executor函数,并把处理的函数push进入callback数组中
  • 在reslove或reject执行时,我们去执行callback中的函数
  • 我们可以看到同样一个函子a在不同时期有着不一样的状态。
  • 显然如果在reslove()或者 reject( )之后我们再添加then()方法是不会有作用的

那么我们如何解决reslove之后a函子的then调用问题呢,其实reslove之后,我们已经有了value值,那不就是我们最开始讲的普通函子的链式调用吗?所以现在我们只需要标记出,函子此时的状态,再决定如何调用then就好啦

4 增加Promise状态

  • 我们定义进行中的状态为pending
  • 已成功执行后为fulfilled
  • 失败为rejected
class MyPromise {constructor (executor) {let _this = this;this.status = 'pending';this.value = undefined;this.reason = undefined;this.onResolvedCallbacks = [];this.onRejectedCallbacks = [];function resolve(value){if (_this.status === 'pending') {_this.status = 'fulfilled';_this.value = value;_this.onResolvedCallbacks.forEach(item => item())}}function reject(reason){if (_this.status === 'pending') {_this.status = 'rejected';  _this.reason = reason;_this.onRejectedCallbacks.forEach(item => item());}}executor(resolve, reject);} then (fn,fn2) {return new MyPromise((resolve,reject) => {if(this.status === 'pending'){this.onResolvedCallbacks.push (()=>{let data =  fn(this.value) console.log(data)         resolve(data)})this.onRejectedCallbacks.push (()=>{let reason =  fn2(this.reason) console.log(reason)         reject(reason)})}if(this.status === 'fulfilled'){let x = fn(this.value)resolve(x)}if(this.status === 'rejected'){let x = fn2(this.value)reject(x)}})}
}var a = asyncReadFile('./data.txt');
a.then(add,mismanage).then(add,mismanage).then(add,mismanage);
复制代码

我们分析一下上面这个过程

其实就多了一个参数,然后判断了一下,很简单。那么我们现在来分析一下,当我们调用fulfilled状态下的a的执行过程

setTimeout(()=>{ d = a.then(add);} ,2000)
value:"1"
复制代码
  • (1)先执行new MyPromise(),初始化d
  • (2)然后执行 executor(resolve, reject);fn开始执行,算出新的值x
  • (3)传给d的resolve执行,
  • (4)修改stauts和value的状态
  • (5)return 出新的函子,可以继续链式调用

我们来想一个问题,如果(2)中fn是一个异步操作,d后边继续调用then方法,此刻pending状态就不会改变,直到resolve执行。那么then的方法就会加到callback上。就又回到我们之前处理异步的状态啦。所以这就是为什么Promise能够解决回调地狱

参考代码:promise5.js

好了,我们现在来看传进去的方法fn(this.value) ,我们需要用上篇讲的Maybe函子去过滤一下。

5 Maybe函子优化

 then (onResolved,onRejected) {onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}return new MyPromise((resolve,reject) => {if(this.status === 'pending'){this.onResolvedCallbacks.push (()=>{let x =  onResolved(this.value) resolve(x)})this.onRejectedCallbacks.push (()=>{let x =  onRejected(this.reason)reject(x)})}if(this.status === 'fulfilled'){let x = onResolved(this.value)resolve(x)}if(this.status === 'rejected'){let x = onRejected(this.value)reject(x)}})}
复制代码
  • Maybe函子很简单,对onResolved和onRejected进行一下过滤

参考代码:promise6.js

这一篇先写到这里吧。最后总结一下,Promise的功能很强大,就是少年派的奇幻漂流一样。虽然旅程绚烂多彩,但始终陪伴你的只有那只老虎。Promise也是一样,只要掌握其核心函子的概念,其他问题就比较好理解啦。这里只实现了一个简单的Promise,更强大的功能,我们慢慢加吧。

函数式编程之Promise的奇幻漂流相关推荐

  1. 异步编程之Promise(2):探究原理

    异步编程系列教程: (翻译)异步编程之Promise(1)--初见魅力 异步编程之Promise(2):探究原理 异步编程之Promise(3):拓展进阶 异步编程之Generator(1)--领略魅 ...

  2. java8函数式编程之Stream流处理的方法和案例讲解

    函数式编程最早是数学家阿隆佐·邱奇研究的一套函数变换逻辑,又称Lambda Calculus(λ-Calculus),所以也经常把函数式编程称为Lambda计算. 为什么Java需要Lambda表达式 ...

  3. 函数式编程之pipeline——很酷有没有

    Pipeline pipeline 管道借鉴于Unix Shell的管道操作--把若干个命令串起来,前面命令的输出成为后面命令的输入,如此完成一个流式计算.(注:管道绝对是一个伟大的发明,他的设哲学就 ...

  4. 函数式编程之-bind函数

    Bind函数 Bind函数在函数式编程中是如此重要,以至于函数式编程语言会为bind函数设计语法糖.另一个角度Bind函数非常难以理解,几乎很少有人能通过简单的描述说明白bind函数的由来及原理. 这 ...

  5. python函数式编程之functools、itertools、operator详解

    文章目录 写在篇前 itertools 无穷迭代器 最短停止迭代器 排列组合迭代器 operator 基本运算符函数 属性查询 functools partial & partialmetho ...

  6. return编程python_python3 第二十一章 - 函数式编程之return函数和闭包

    我们来实现一个可变参数的求和.通常情况下,求和的函数是这样定义的: def calc_sum(*args): ax=0for n inargs: ax= ax +nreturn ax 但是,如果不需要 ...

  7. 前端异步编程之Promise和async的用法

    传统的异步解决方案采用回调函数和事件监听的方式,而这里主要记录两种异步编程的新方案: ES6的新语法Promise ES2017引入的async函数 Generator函数(略) Promise的含义 ...

  8. 详解Python函数式编程之map、reduce、filter

    map().reduce().filter()是Python中很常用的几个函数,也是Python支持函数式编程的重要体现.不过,在Python 3.x中,reduce()不是内置函数,而是放到了标准库 ...

  9. 【Java八股文之基础篇(十九)】函数式编程之Stream流(上)

    Stream流 概述 Java8的Stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链状流式的操作.可以更方便的让我们对集合或数组操作. 案例数据准备 <depe ...

  10. 函数式编程之every函数

    这是一系列关于函数式编程的文章,主要记录我在函数式编程学习方面的心得体会,部分参考于部分书籍或者文章,纯粹个人学习,不做任何商业用途,如有冒犯请及时指正. es6 every函数 every函数我们在 ...

最新文章

  1. JavaScript 工作原理之十一-渲染引擎及性能优化小技巧
  2. 每日记载内容总结44
  3. Linux平台定时监控进程退出并自动重启的方法
  4. Visual Studio 32位64位的问题和如何编译32位64位工程的问题
  5. UVALive - 7270 Osu! Master
  6. 数据库关机_数据库:MySQL常见的三种存储引擎InnoDB、MyISAM、MEMORY的区别?
  7. 用实际的软件编程示例解释C#中的装饰器模式
  8. Android范围自定义,android – 如何在dagger 2.10中创建自定义范围模块
  9. 数学系教材推荐(转载)
  10. 以WIN10 22H2为例,下载ISO、制作安装U盘的办法
  11. 用c#实现拍拍抢拍精灵实现过程--核心代码--腾讯qq拍拍网秒杀器代码
  12. mysql数据库输入窗体vbs代码_VBS教程:VBScript 与窗体
  13. 计算机出现假桌面怎么解决办法,win10专业版系统桌面经常假死解决办法
  14. 百度收录如何API提交(java、python)
  15. win10虚拟机环境下运行驱动程序
  16. 各种主题瓦片地图在线资源访问总结
  17. 全球及中国共享汽车市场规模预测与投资机遇研究报告2022版
  18. php网站后台开发教程,WordPress做网站后台开发教程
  19. dvdfab虚拟光驱使用教程
  20. DB2 V9.7新特性 - 降低高水位标记

热门文章

  1. 2021-11-24【数据结构练习题】【二叉搜索树的插入删除】
  2. python+opencv实现图像拼接
  3. Samba安装,你可能没有权限使用网络资源。请与这台服务器的管理员联系。。。。。。
  4. QT下以ADO连接ORACLE数据库
  5. C语言 数组排序 打乱 查找
  6. 机动车污染排放检验信息系统信息化建设目标及规范
  7. 按计算机应用领域分类,按计算机用途分类
  8. 如何将PDF转换成word文档
  9. PDF文件怎么转换成word?
  10. ORACLE解决表空间不释放空间