这一次,彻底理解Promise源码思想
关于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源码思想的更多相关文章
- 深入理解OkHttp源码(一)——提交请求
本篇文章主要介绍OkHttp执行同步和异步请求的大体流程.主要流程如下图: 主要分析到getResponseWidthInterceptorChain方法,该方法为具体的根据请求获取响应部分,留着后面 ...
- [区块链\理解BTCD源码]GO语言实现一个区块链原型
摘要 本文构建了一个使用工作量证明机制(POW)的类BTC的区块链.将区块链持久化到一个Bolt数据库中,然后会提供一个简单的命令行接口,用来完成一些与区块链的交互操作.这篇文章目的是希望帮助大家理解 ...
- 深入理解OkHttp源码(三)——网络操作
这篇博客侧重于了解OkHttp的网络部分,包括Socket的创建.连接,连接池等要点.OkHttp对Socket的流操作使用了Okio进行了封装,本篇博客不做介绍,想了解的朋友可以参考拆轮子系列:拆O ...
- 深入理解OkHttp源码(二)——获取响应
首先先看一张流程图,该图是从拆轮子系列:拆 OkHttp 中盗来的,如下: 在上一篇博客深入理解OkHttp源码(一)——提交请求中介绍到了getResponseWithInterceptorChai ...
- 七、Spring之深入理解AOP源码
Spring之深入理解AOP源码 在上一篇博文中,我们对AOP有了初步的了解,那么接下来我们就对AOP的实现原理进行深入的分析. 在之前写的那个AOP示例代码当中有这样一个注解:@Enable ...
- 史上最完整promise源码手写实现
史上最完整的promise源码实现,哈哈,之所以用这个标题,是因为开始用的标题<手写promise源码>不被收录 promise自我介绍 promise : "君子一诺千金,承诺 ...
- Promise源码实现与测试
const PENDING = 'pending', FULFILLED = 'fulfilled', REJECTED = 'rejected' class MyPromise { construc ...
- promise源码解析
前言 大部分同学对promise,可能还停留在会使用es6的promise,还没有深入学习.我们都知道promise内部通过reslove.reject来判断执行哪个函数,原型上面的then同样的,也 ...
- Promise 源码分析
前言 then/promise项目是基于Promises/A+标准实现的Promise库,从这个项目当中,我们来看Promise的原理是什么,它是如何做到的,从而更加熟悉Promise 分析 从ind ...
随机推荐
- com.rabbitmq.client.impl.ForgivingExceptionHandler.log:119 -An unexpected connection driver error occured
在服务器上安装了一个RabbitMq,并新创建了一个用户授予了管理员角色,登录控制台查看一切正常,兴高采烈启动项目进行连接,结果一盆冷水下来,报如下错误: o.s.a.r.l.SimpleMessag ...
- mysql uuid使用
java中可以使用UUID类来生成uuid,使用mysql也可以使用UUID函数来获取uuid,如 select UUID(); 也可以对查询的结果做一些处理,比如说将"-"替换成 ...
- ibatis 学习
1.$与#的区别: 用到#和$来获取传的参数值,其中#是将传来的值替换(如果是字符串,会将‘’带着替换,比如上面$value$,用#value#的话,就会出错.#与$的使用区别:$中间的变量就是直接替 ...
- 自定义属性--JavaScript
1 - 获取属性值 element.属性 获取属性值 element.getAttribute('属性') 区别: element.属性 --获取内置属性(元素本身自带的属性) element.get ...
- 基于C#的机器学习--机器学习建模的基础
构建ML模型的步骤 现在我们已经看了解到了一些ML应用程序的例子,问题是,我们如何构建这样的ML应用程序和系统? 下图总结了我们使用ML开发应用程序的方法,我们将在下面更详细地讨论这个问题: 如上图所 ...
- 使用 .NET Core 3.0 的 AssemblyLoadContext 实现插件热加载
一般情况下,一个 .NET 程序集加载到程序中以后,它的类型信息以及原生代码等数据会一直保留在内存中,.NET 运行时无法回收它们,如果我们要实现插件热加载 (例如 Razor 或 Aspx 模版的热 ...
- React的diff算法(译文)
前言 此篇文章主要是因为在看Virtual DOM(虚拟DOM)的时候看到的主要讲的是实现Virtual Dom 的diff算法,原文地址:https://calendar.perfplanet.c ...
- 谷歌助力,快速实现 Java 应用容器化
原文地址:梁桂钊的博客 博客地址:http://blog.720ui.com 欢迎关注公众号:「服务端思维」.一群同频者,一起成长,一起精进,打破认知的局限性. Google 在 2018 年下旬开源 ...
- AlexNet模型的解析及tensorflow实现
AlexNet是ImageNet LSVRC 2012比赛中分类效果第一的深度神经网络模型,点击链接下载论文http://papers.nips.cc/paper/4824-imagenet-clas ...
- 最强最全的Java后端知识体系
目录 最全的Java后端知识体系 Java基础 算法和数据结构 Spring相关 数据库相关 方法论 工具清单 文档 @(最强最全的Java后端知识体系) 最全的Java后端知识体系 最全的Java后 ...