原文链接:https://geniuspeng.github.io/2017/12/14/my-promise/

关于Promise的概念以及意义就不在这里介绍了,最近看到了一些实现Promise的核心思想,在这里整理一下。写这篇文章并不是为了实现一个自己的Promise,毕竟现在es6已经标准支持,而且还有一大堆的第三方Promise库,主要是为了从最底层的角度深入理解一下Promise的实现思路。

1.从状态机出发

我们知道,Promise的作用在于包裹一个异步(或同步)操作,然后通过then方法实现这个操作成功(或失败)的回调,而这其中的原理则是可以通过一个类似状态机的机制来控制。首先需要明确几个概念,这些概念可以从Promise/A的API规范中找到:

  • Promise(中文:承诺)其实为一个有限状态机,共有三种状态:pending(执行中)、fulfilled(执行成功)和rejected(执行失败)。

  • 其中pending为初始状态,fulfilled和rejected为结束状态(结束状态表示promise的生命周期已结束)。

  • 状态转换关系为:fulfill方法(pending->fulfilled),reject(pending->rejected),此状态转换不可逆。

  • 随着状态的转换将触发各种事件(如执行成功事件、执行失败事件等)。

根据这些信息,我们就可以得出一个Promise的初始模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2; function Promise() {
//存储状态:PENDING, FULFILLED or REJECTED
let state = PENDING; //存储 FULFILLED 或者 REJECTED时传入的参数value
let value = null; // 存储
let handlers = []; function fulfill(result) {
state = FULFILLED;
value = result;
} function reject(error) {
state = REJECTED;
value = error;
}
}

2.实现handle方法

有了上面的初始模型,接下来需要一个resolve方法,我们知道new Promise()的时候传入的参数是一个function,而这个function有两个参数resolve和reject,并且这两个参数都是一个function,就是说是一个带了两个function参数的function。而里面的resolve和reject方法决定了这个Promise的走向(fulfill方法还是reject),我们的handle方法就是决定这个走向用的,说起来有点绕,看一下handle大致做了什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2; function Promise(fn) {
//存储状态:PENDING, FULFILLED or REJECTED
let state = PENDING; //存储 FULFILLED 或者 REJECTED时传入的参数value
let value = null; // 存储
let defers = null; function fulfill(result) {
state = FULFILLED;
value = result;
handle(defers);
} function reject(error) {
state = REJECTED;
value = error;
handle(defers);
} function handle(handler) {
if (state === PENDING) {
defers = handler;
} else {
if (state === FULFILLED) {
//do sth fulfilled
}
if (state === REJECTED) {
//do sth rejected
}
}
}
fn(fulfill, reject);
}

3.实现then方法

then方法接受两个参数,分别是fulfill方法之后的回调onResolved以及reject之后的回调onRejected。同时因为Promise内的方法可能是同步页可能是异步,为了保证handle都能正常执行,我们需要一个defers变量,这样同步情况下,fulfill方法不会执行handle,而是到then的时候再执行handle,处于一个完全同步状态,而异步情况,在pending的时候将相应的handler存到defer中,直到fulfill的时候去进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2; function Promise(fn) {
//存储状态:PENDING, FULFILLED or REJECTED
let state = PENDING; //存储 FULFILLED 或者 REJECTED时传入的参数value
let value = null; //存储异步情况相应的handler
let defers = null; function fulfill(result) {
state = FULFILLED;
value = result;
if (defers) {
handle(defers);
} } function reject(error) {
state = REJECTED;
value = error;
if (defers) {
handle(defers);
}
} function handle(handler) {
if (state === PENDING) {
defers = handler;
} else {
if (state === FULFILLED) {
handler.onFulfilled(value);
}
if (state === REJECTED) {
handler.onRejected(value);
}
}
}
this.then = function(onFulfilled, onRejected) {
handle({
onFulfilled,
onRejected
});
};
fn(fulfill, reject);
}

到这里基本的逻辑已经出来了,但实际上还有很多可以改进的地方。比如Promise不仅仅可以接收一个单独的值,同样可以接收一个Promise对象,而then的返回值也是一个Promise对象,也就是完全支持链式调用。下面从这两个角度出发,进行完善。

支持Promise参数

resolve 既可以接受一个 Promise,也可以接受一个基本类型。当 resolve 一个 Promise 时,就成了酱紫:

1
2
3
4
5
6
7
8
new Promise(function(resolve, reject) {
resolve(new Promise(function(resolve, reject) {
resolve('aaa')
}))
})
.then(function(v) {
console.log(v);
})

我们上面的方法就无法达到效果,这时我们需要一个新的方法来进行改进,可以把这个方法就叫做resolve,而之前的fulfill方法我们仅仅当做是一个改变state状态的方法,也就是说简单来说,resolve其实就是改进了一下fulfill,那么最开始其实就是 这样:

1
2
3
4
function resolve(result) {
fulfill(result)
}
fn(resolve, reject);

嗯就这样,下面开始改进。因为resolve可能接收一个Promise对象,Promise一定有then方法,我们可以对这点进行一个判断,是否为Promise对象进行不同的处理,同时还需要一个doResolve方法进行对传入的Promise对象的递归处理(因为传入的Promise对象rosolve的可能还是个Promise对象,不一定嵌套了多少层- -)…这两个方法实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function getThen(obj) {
try {
return obj.then;
} catch (e) {
throw new Error(e);
return null;
}
}
function doResolve (fn, onFulfilled, onRejected) {
let done = false
try {
fn(function (value) {
if (done) return
done = true
// 执行由 resolve 传入的 resolve 回调
onFulfilled(value)
}, function (reason) {
if (done) return
done = true
onRejected(reason)
})
} catch (ex) {
if (done) return
done = true
onRejected(ex)
}
}
function resolve (result) {
try {
let then = getThen(result)
if (then) {
// 递归 resolve 待解析的 Promise
doResolve(then.bind(result), resolve, reject)
return
}
fulfill(result)
} catch (e) {
reject(e)
}
}

getThen其实很简单,就是判断这个对象是否有then方法,如果有,我们当做Promise处理,通过doResolve来递归resolve方法,直到遇到的不是Promise对象为止,resolve出真正的值。而doResolve大致就是一个tryCallTwo的功能,让第一个fn参数以后两个参数为参数去执行,即做了最开始fn(resolve, reject)的工作,类似这样:

1
2
3
4
5
6
7
function tryCallTwo(fn, a, b) {
try {
fn(a, b);
} catch (ex) {
return ex;
}
}

同时doResolve需要确保 onFulfilled 与 onRejected 只会被调用一次,而我们的最后一行的fn(resolve, reject)需要替换成doResolve(fn, resolve, reject),至此为止,完整的实现为(getThen和doResolve做为两个辅助函数,为了更清晰,我们放在Promise外面):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
function getThen(obj) {
try {
return obj.then;
} catch (e) {
throw new Error(e);
return null;
}
}
function doResolve (fn, onFulfilled, onRejected) {
let done = false
try {
fn(function (value) {
if (done) return
done = true
// 执行由 resolve 传入的 resolve 回调
onFulfilled(value)
}, function (reason) {
if (done) return
done = true
onRejected(reason)
})
} catch (ex) {
if (done) return
done = true
onRejected(ex)
}
}
const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2; function Promise(fn) {
//存储状态:PENDING, FULFILLED or REJECTED
let state = PENDING; //存储 FULFILLED 或者 REJECTED时传入的参数value
let value = null; //存储异步情况相应的handler
let defers = null; function fulfill(result) {
state = FULFILLED;
value = result;
if (defers) {
handle(defers);
} } function reject(error) {
state = REJECTED;
value = error;
if (defers) {
handle(defers);
}
} function resolve (result) {
try {
let then = getThen(result)
if (then) {
// 递归 resolve 待解析的 Promise
doResolve(then.bind(result), resolve, reject)
return
}
fulfill(result)
} catch (e) {
reject(e)
}
} function handle(handler) {
if (state === PENDING) {
defers = handler;
} else {
if (state === FULFILLED) {
handler.onFulfilled(value);
}
if (state === REJECTED) {
handler.onRejected(value);
}
}
}
this.then = function(onFulfilled, onRejected) {
handle({
onFulfilled,
onRejected
});
};
doResolve(fn, resolve, reject)
}

优化handle方法

这里的handle方法有一点小问题,首先,handle传入参数handler,这个handler这里可以看出是then带过来的一个对象,其中包含了成功和拒绝两个状态的回调onFulfilled, onRejected,当然我们在then中完全可以不写参数(虽然这样没有了then的意义),比如这样

1
2
3
new Promise(function(resolve, reject) {
resolve('sth')
}).then();

这种情况会因为handler.onFulfilled或者handler.onRejected不存在而导致报错,于是可以把handle方法稍微优化一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function handle(handler) {
if (state === PENDING) {
defers = handler;
} else {
let handlerCallback;
if (state === FULFILLED) {
handlerCallback = handler.onFulfilled || null;
} else {
handlerCallback = handler.onRejected || null;
} if (handlerCallback) {
handlerCallback(value);
}
}
}

通过一个handlerCallback存储回调。。。没有的时候,就什么也不做就可以了。

实现链式调用

我们都知道,then方法的回调可以return一个值,而then方法本身返回的是一个Promise,而这个Promise中resolve的结果就是这个return的值,所以可以实现完整的链式调用,对于这一点,我们只需要把上述的then方法稍微加工一下,让它返回一个Promise:

1
2
3
4
5
6
7
8
9
10
this.then = function(onFulfilled, onRejected) {
return new Promise(function(resolve, reject) {
handle({
onFulfilled,
onRejected,
resolve,
reject
});
})
};

then方法改了之后,handle方法也需要进行一些改进,因为此时handle的参数handler不仅仅有onFulfilled, onRejected这两个回调,还需要传入then返回的Promise的reslove和reject方法。而传给下一个then(链式)的值就是这个resolve所解析出来的value,所以在这个handle方法的最后,一定要resolve这个value。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function handle(handler) {
if (state === PENDING) {
defers = handler;
} else {
let handlerCallback = null;
if (state === FULFILLED) {
handlerCallback = handler.onFulfilled;
} else {
handlerCallback = handler.onRejected;
} if (!handlerCallback) {
if (state === FULFILLED) {
handler.resolve(value);
} else {
handler.reject(value);
}
return;
} const ret = handlerCallback(value);
handler.resolve(ret);
}
}

把优化后的再整理一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
function getThen(obj) {
try {
return obj.then;
} catch (e) {
throw new Error(e);
return null;
}
}
function doResolve (fn, onFulfilled, onRejected) {
let done = false
try {
fn(function (value) {
if (done) return
done = true
// 执行由 resolve 传入的 resolve 回调
onFulfilled(value)
}, function (reason) {
if (done) return
done = true
onRejected(reason)
})
} catch (ex) {
if (done) return
done = true
onRejected(ex)
}
}
const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2; function Promise(fn) {
//存储状态:PENDING, FULFILLED or REJECTED
let state = PENDING; //存储 FULFILLED 或者 REJECTED时传入的参数value
let value = null; //存储异步情况相应的handler
let defers = null; function fulfill(result) {
state = FULFILLED;
value = result;
if (defers) {
handle(defers);
} } function reject(error) {
state = REJECTED;
value = error;
if (defers) {
handle(defers);
}
} function resolve (result) {
try {
let then = getThen(result)
if (then) {
// 递归 resolve 待解析的 Promise
doResolve(then.bind(result), resolve, reject)
return
}
fulfill(result)
} catch (e) {
reject(e)
}
} function handle(handler) {
if (state === PENDING) {
defers = handler;
} else {
let handlerCallback = null;
if (state === FULFILLED) {
handlerCallback = handler.onFulfilled;
} else {
handlerCallback = handler.onRejected;
} if (!handlerCallback) {
if (state === FULFILLED) {
handler.resolve(value);
} else {
handler.reject(value);
}
return;
} const ret = handlerCallback(value);
handler.resolve(ret);
}
}
this.then = function(onFulfilled, onRejected) {
return new Promise(function(resolve, reject) {
handle({
onFulfilled,
onRejected,
resolve,
reject
});
})
};
doResolve(fn, resolve, reject)
}

到此为止,基本上的功能已经完成了,当然,ES6标注的Promise还有Promise.all,Promise.race等方法,不过只要Promise的基本实现原理弄明白了,这些扩展起来也是很简单事情。
还有一点需要注意,Promise内部是纯异步实现的,即使是同步直接传入一个resolve值也会是异步完成,在ES6标准中的Promise是通过Job Queue来完成(可以参考以前的文章Micro-task),在这里我们可以通过setTimeout来简单模拟一下,虽然不完全相同(setTimeout属于Macro-task),但是可以大体上实现纯异步的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function handle(handler) {
if (state === PENDING) {
defers = handler;
} else {
setTimeout(() => {
let handlerCallback = null;
if (state === FULFILLED) {
handlerCallback = handler.onFulfilled;
} else {
handlerCallback = handler.onRejected;
} if (!handlerCallback) {
if (state === FULFILLED) {
handler.resolve(value);
} else {
handler.reject(value);
}
return;
} const ret = handlerCallback(value);
handler.resolve(ret);
}, 0)
}
}

嗯就酱~~~~参考链接:

文末福利:

福利一:前端,Java,产品经理,微信小程序,Python等10G资源合集大放送:https://www.jianshu.com/p/e8197d4d9880

福利二:微信小程序入门与实战全套详细视频教程。

【领取方法】

关注 【编程微刊】微信公众号:

回复【小程序demo】一键领取130个微信小程序源码demo资源。

回复【领取资源】一键领取前端,Java,产品经理,微信小程序,Python等资源合集10G资源大放送。

从源码角度实现一个自己的Promise的更多相关文章

  1. 从源码角度深入理解Toast

    Toast这个东西我们在开发中经常用到,使用也很简单,一行代码就能搞定: 1: Toast.makeText(", Toast.LENGTH_LONG).show(); 但是我们经常会遇到这 ...

  2. Android -- 带你从源码角度领悟Dagger2入门到放弃

    1,以前的博客也写了两篇关于Dagger2,但是感觉自己使用的时候还是云里雾里的,更不谈各位来看博客的同学了,所以今天打算和大家再一次的入坑试试,最后一次了,保证最后一次了. 2,接入项目 在项目的G ...

  3. Android -- 带你从源码角度领悟Dagger2入门到放弃(二)

    1,接着我们上一篇继续介绍,在上一篇我们介绍了简单的@Inject和@Component的结合使用,现在我们继续以老师和学生的例子,我们知道学生上课的时候都会有书籍来辅助听课,先来看看我们之前的Stu ...

  4. 从源码角度入手实现RecyclerView的Item点击事件

    RecyclerView 作为 ListView 和 GridView 的替代产物,相信在Android界已广为流传. RecyclerView 本是不会有类似 ListView 的那种点击事件,但是 ...

  5. 从template到DOM(Vue.js源码角度看内部运行机制)

    写在前面 这篇文章算是对最近写的一系列Vue.js源码的文章(https://github.com/answershuto/learnVue)的总结吧,在阅读源码的过程中也确实受益匪浅,希望自己的这些 ...

  6. Android布局性能优化—从源码角度看ViewStub延迟加载技术

    在项目中,难免会遇到这种需求,在程序运行时需要动态根据条件来决定显示哪个View或某个布局,最通常的想法就是把需要动态显示的View都先写在布局中,然后把它们的可见性设为View.GONE,最后在代码 ...

  7. Android进阶:二、从源码角度看透 HandlerThread 和 IntentService 本质

    上篇文章我们讲日志的存储策略的时候用到了HandlerThread,它适合处理"多而小的任务"的耗时任务的时候,避免产生太多线程影响性能,那这个HandlerThread的原理到底 ...

  8. JVM源码分析之一个Java进程究竟能创建多少线程

    JVM源码分析之一个Java进程究竟能创建多少线程 原创: 寒泉子 你假笨 2016-12-06 概述 虽然这篇文章的标题打着JVM源码分析的旗号,不过本文不仅仅从JVM源码角度来分析,更多的来自于L ...

  9. Android -- 带你从源码角度领悟Dagger2入门到放弃(一)

    1,以前的博客也写了两篇关于Dagger2,但是感觉自己使用的时候还是云里雾里的,更不谈各位来看博客的同学了,所以今天打算和大家再一次的入坑试试,最后一次了,保证最后一次了. 2,接入项目 在项目的G ...

随机推荐

  1. error c2572重定义默认參数

    因为想省事.在声明过函数之后直接复制粘贴去实现,结果出现error c2572重定义默认參数 顾名思义.该默认參数被定义多次.在一个文件(或一个作用域)中,仅仅能为形參指定默认 实參一次.在编译的时候 ...

  2. Fragment-按返回键程序退出

    今天在做fragment的时候,发现一个问题,当我的界面停留在fragment的时候,按物理返回键,这时候会推出整个应用.这当然不是我们期望的,我们期望按返回键以后,应用界面返回添加fragment之 ...

  3. 最值(min、max)与极值的理解

    max(a,b)=−min(−a,−b) 如果 a≥b ⇒ max(a,b)=a,−a≤−b,⇒ 同理 min(a,b)=−max(−a,−b) 1. 最值 最小:不能更少,如果是整数关系的话,也即从 ...

  4. ps的入门

    ps的入门 http://www.cnblogs.com/qingci/archive/2012/09/20/2694728.html

  5. goinstall

    [背景] 折腾: [记录]go语言中通过log4go实现同时输出log信息到log文件和console 期间,以: http://code.google.com/p/log4go/ 为例,如何安装第三 ...

  6. WPF MVVM示例自定义模板数据绑定

    在触摸屏设备上.由于列表是的信息展示不是非常直观和便捷操作. 所以也就出现了很多用面板控件:类似win10的Metro风格, 所以抽空做了一个WPF面板控件. 话不多上 , 先上一个示例图. 为了便于 ...

  7. ImageButton-设置background跟src

    xml中添加ImageButton的background跟src <ImageButton android:id="@+id/tv3" android:layout_widt ...

  8. jquery实现转盘抽奖

    jquery实现转盘抽奖 一.总结 一句话总结:这里环形转盘,环形的东西可以化成线性的,然后访问到哪个,给他加上背景为红的样式,用定时器开控制转盘的速度,函数执行的时间间隔越小,就运动的越快. 1.如 ...

  9. ua识别(浏览器标识识别)

    ua识别(浏览器标识识别) 一.总结 1.浏览器标识(UA):可以使得服务器能够识别客户使用的操作系统及版本.CPU 类型.浏览器及版本.浏览器渲染引擎.浏览器语言.浏览器插件,从而判断用户是使用电脑 ...

  10. cv2.putText 文字换行('\n')无法解析换行

    OpenCV putText() new line character cv2.putText 在向图像中添加文本信息时,如果在待添加的文本中含有换行转义符,一般它是无法正确处理的: cv2.putT ...