关于Promise的源码实现,网上有太多答案,我也看过很多资料,但都不是很明白。直到有一天我学完函数式编程之函子的概念,才对Promise源码有了更深刻的认识。今天,就让我们来重新认识一下Promise。

我们知道,Promise的诞生是为了解决“回调地狱”的问题,它用同步链式的方式去解决异步的嵌套回调。

啥?同步链式?这不就是我们上一节学习的函子的思想吗?如果对函子有所了解,那么再来学习Promise源码就比较容易理解了。接下来,我们探究一下函子和Promise有着怎样的关系。

实现一个简单的Promise函子

先来回顾一下函子Functor的链式调用:

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 函数。通过 map 中传递进去的函数fn去处理函子保存的数据,用得到的值去生成新的函子。

等等...函子是同步链式,而Promise是异步链式。也就是说上面a的值是异步产生的,那我们该何如传入 this.value 值呢?

function executor(resolve){
setTimeout(()=>{ resolve(100) },500)
}

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

class MyPromise {
constructor (executor) {
let self = this;
this.value = undefined; // 回调函数,用来赋值给 value
function resolve(value){
self.value = value;
}
executor(resolve)
}
} var a = new MyPromise(executor);

解释一下上面的代码:我们将 executor 传入并立即执行,在 resolve 回调函数中我们能够拿到 value 值,我们定义 resolve 回调函数将 value 的值赋给 this.value。

这样我们就轻松的完成了 a 这个对象的赋值。由于是异步得到的,那么我们怎么用方法去处理这个数据呢?

根据函子的思想,在拿到数据之后,我们应该让 map 里传入的 fn 函数去处理数据。由于是异步处理, resolve 执行后才拿到数据,所以我们定义了一个 callback 函数,在 callback 里面执行 fn。最后把 fn 处理的结果交给下一个函子的 resolve 保存。

class MyPromise {
constructor (executor) {
let self = this;
this.value = undefined;
this.callback = null;
// 回调函数,用来赋值给 value
function resolve(value){
self.value = value
self.callback() // 得到 value 之后,在 callback 里面执行 map 传入的 fn 函数处理数据
}
executor(resolve)
} map (fn) {
let self = this;
return new MyPromise((resolve) => {
self.callback = function(){
let data = fn(self.value)
resolve(data)
}
})
}
} new MyPromise(executor).map(add1).map(add1)

同时调用同一个Promise函子

Promise除了能链式调用,还能同时调用,比如:

var a = new MyPromise(executor);
var b = a.map(add);
var c = a.map(minus);

像上面这个同时调用a这个函子。你会发现,它实际上只执行了c。原因也很简单,b先给a的 callback 赋值,然后c又给a的 callback 赋值。所以把b给覆盖掉了就不会执行啦。解决这个问题很简单,我们只需要让callback变成一个数组就解决了。

class MyPromise {
constructor (executor) {
let self = this;
this.value = undefined;
this.callbacks = [];
function resolve(value){
self.value = value;
self.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;

增加reject回调函数

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

class MyPromise {
constructor (executor) {
let self = this;
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
function resolve(value){
self.value = value;
self.onResolvedCallbacks.forEach(item => item())
}
function reject(reason){
self.reason = reason;
self.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

这时候将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函数的过程,这个过程有没有觉得在哪见过。仔细看下,asyncReadFile 不就是前面我们提到的柯里化。

增加Promise状态

我们定义进行中的状态为pending

已成功执行后为fulfilled

失败为rejected

class MyPromise {
constructor (executor) {
let self = this;
this.status = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
function resolve(value){
if (self.status === 'pending') {
self.status = 'fulfilled';
self.value = value;
self.onResolvedCallbacks.forEach(item => item())
}
}
function reject(reason){
if (self.status === 'pending') {
self.status = 'rejected';
self.reason = reason;
self.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);

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

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进行一下过滤。

总结

Promise是一个很不好理解的概念,但总归核心思想还是函子。

同时,在函子的基础上增加了一些异步的实现。异步的实现是一个比较费脑细胞的点,把加粗的字体花点时间多思考思考,加油!

参考链接:函数式编程之Promise的奇幻漂流

标准PromiseA+规范实现:这一次,彻底弄懂 Promise 原理

这一次,彻底理解Promise源码思想的更多相关文章

  1. 深入理解OkHttp源码(一)——提交请求

    本篇文章主要介绍OkHttp执行同步和异步请求的大体流程.主要流程如下图: 主要分析到getResponseWidthInterceptorChain方法,该方法为具体的根据请求获取响应部分,留着后面 ...

  2. [区块链\理解BTCD源码]GO语言实现一个区块链原型

    摘要 本文构建了一个使用工作量证明机制(POW)的类BTC的区块链.将区块链持久化到一个Bolt数据库中,然后会提供一个简单的命令行接口,用来完成一些与区块链的交互操作.这篇文章目的是希望帮助大家理解 ...

  3. 深入理解OkHttp源码(三)——网络操作

    这篇博客侧重于了解OkHttp的网络部分,包括Socket的创建.连接,连接池等要点.OkHttp对Socket的流操作使用了Okio进行了封装,本篇博客不做介绍,想了解的朋友可以参考拆轮子系列:拆O ...

  4. 深入理解OkHttp源码(二)——获取响应

    首先先看一张流程图,该图是从拆轮子系列:拆 OkHttp 中盗来的,如下: 在上一篇博客深入理解OkHttp源码(一)——提交请求中介绍到了getResponseWithInterceptorChai ...

  5. 七、Spring之深入理解AOP源码

    Spring之深入理解AOP源码 ​ 在上一篇博文中,我们对AOP有了初步的了解,那么接下来我们就对AOP的实现原理进行深入的分析. ​ 在之前写的那个AOP示例代码当中有这样一个注解:@Enable ...

  6. 史上最完整promise源码手写实现

    史上最完整的promise源码实现,哈哈,之所以用这个标题,是因为开始用的标题<手写promise源码>不被收录 promise自我介绍 promise : "君子一诺千金,承诺 ...

  7. Promise源码实现与测试

    const PENDING = 'pending', FULFILLED = 'fulfilled', REJECTED = 'rejected' class MyPromise { construc ...

  8. promise源码解析

    前言 大部分同学对promise,可能还停留在会使用es6的promise,还没有深入学习.我们都知道promise内部通过reslove.reject来判断执行哪个函数,原型上面的then同样的,也 ...

  9. Promise 源码分析

    前言 then/promise项目是基于Promises/A+标准实现的Promise库,从这个项目当中,我们来看Promise的原理是什么,它是如何做到的,从而更加熟悉Promise 分析 从ind ...

随机推荐

  1. spring 定时器知识点

    一.各域说明 字段域 秒 分 时 日 月 星期(7为周六) 年(可选) 取值范围 0-59 0-59 0-23 1-31 1-12或JAN–DEC 1-7或SUN–SAT 1970–2099 可用字符 ...

  2. 【SQL server】SQL Server 触发器

    触发器是一种特殊类型的存储过程,它不同于之前的我们介绍的存储过程.触发器主要是通过事件进行触发被自动调用执行的.而存储过程可以通过存储过程的名称被调用. Ø 什么是触发器 触发器对表进行插入.更新.删 ...

  3. 冒泡排序--JavaScript描述

    相信凡是编程入门的都接触过冒泡排序算法,排序算法在编程中经常用到. 1. code /** * 冒泡排序 * 1.比较的轮数等于总数 - 1 * 2.比较次数等于要比较的个数 - 1 * --比较从第 ...

  4. gitblit在windows10上的安装及服务启动报错处理

    折腾一下午算是装好了,心情不错决定分享一下.安装步骤大同小异网上都有,主要是Failed creating java 这个报错,百度出来的没有一个能给我解决的,摸索半天找出一个自己的方式.为报错而来的 ...

  5. url设计规范

    一. 什么是RESTful REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移” REST从资源的角 ...

  6. 数据表管理admin

    知识预览 admin组件使用 admin源码解析 回到顶部 admin组件使用 Django 提供了基于 web 的管理工具. Django 自动管理工具是 django.contrib 的一部分.你 ...

  7. JS中的排序算法-冒泡排序解析

    冒泡排序算法 例子:10,8,9,6,4,20,5  从小到大排序 第一轮  1)10>8  交换数据 得到:8,10,9,6,4,20,5 2)10>9  交换数据 得到:8,9,10, ...

  8. LeetCode_844-Backspace String Compare

    输入两个字符串S和T,字符串只包含小写字母和”#“,#表示为退格键,判断操作完退格键剩下字符串是否相等例子:S = “ab#c", T = "ad # c” 返回true,剩下的字 ...

  9. 对比 Git 与 SVN

    一.Git vs SVN Git 和 SVN 孰优孰好,每个人有不同的体验. Git是分布式的,SVN是集中式的 这是 Git 和 SVN 最大的区别.若能掌握这个概念,两者区别基本搞懂大半.因为 G ...

  10. [BZOJ1694/1742/3074]The Cow Run 三倍经验

    Description John养了一只叫Joseph的奶牛.一次她去放牛,来到一个非常长的一片地,上面有N块地方长了茂盛的草.我们可 以认为草地是一个数轴上的一些点.Joseph看到这些草非常兴奋, ...