前言
前几天在项目中遇到回调地狱的情况,但我在刚开始写这个项目的时候还不会用Promise,只知道它可以用来解决回调地狱,所以就用全都用回调函数解决。我是看《深入理解ES6》这本书来学Promise的,在用法和功能上讲得还是挺详细的,但在原理上没怎么讲,所以用的时候有很多地方有疑问,于是在网上找到一篇自己实现Promise的文章来理解Promise实现原理。
参考链接:www.mattgreer.org/articles/pr…
一、最简单的用法
new Promise( (resolve, reject) => {
resolve(42)
}).then( result => {
console.log(result)
})
// 控制台输出
// 42
复制代码
这是一段最简单的Promise使用(我们先不讨论拒绝reject
,假设所有的操作都被接受resolve
),很开心,终于会用Promise了(心虚~),但我在使用的时候出现了一些疑问,在我说我的疑问之前,我们先说一下函数的回调
回调函数
在js中,函数a作为参数传给另一个函数b,此时函数a就叫做回调函数。在函数b的函数体中调用传进来的函数a,此时函数a就被函数b回调了。
OK,现在我们懂什么是回调函数了,我可以说我的疑问了
二、我的疑问 —— resolve
和 then
首先我们先分析一下上面最简单的例子中有多少个回调函数,我把上面的代码重新写一下:
// 现在不用箭头函数,用变量直观点,这里就不写reject了
let fn1 = function (resolve) {
resolve(42)
}
let fn2 = function (result) {
console.log(result)
}
new Promise(fn1).then(fn2) // 42
复制代码
从 new Promise(fn1).then(fn2)
这行代码可以很直观地看出这里有 2 个回调函数,但还没完,再看看fn1
这个函数
let fn1 = function (resolve) {
// resolve再这里被调用了,说明resolve是个回调函数
resolve(42)
}
复制代码
所以现在我们的会调函数是这样的:
在new Promise
的时候给Promise传入了一个含有resolve
这个回调函数的回调函数fn1
,创建完Promise对象之后,随即调用了Promise对象中的then()
方法,给then
方法传入了一个回调函数fn2
。(这里涉及到一点点的new
操作符的作用)
好了,分析完回调函数之后我开始想不通了:
- 疑问1:什么时候调用
fn1
我都不知道 - 疑问2:我没有给
fn1
传回调函数啊 - 疑问3:就先装作我知道
fn1
是什么时候调用的吧,那为什么在fn1
调用resolve
的时候,为什么看起来像调用的是我传过去给then
的回调函数fn2
呢 - 啊?
三、实现原理
终于回到正题了,解决一下我上面的疑问。
在开始的那篇参考的文章中,作者实现了一个还算比较完整的Promise,内容有点长,我就只截取其中能够实现上面写的最简单Promise使用的原理。
function Promise(fn) {
var state = 'pending'; // Promise当前的状态
var value; // 当Promise处于接受状态时保存下来的值,这是结果
var deferred; // 延期执行函数(现在还看不出来它是函数)
function resolve(newValue) { // Promise被接受之后执行的函数
value = newValue; // 保存获取到的结果
state = 'resolved'; // 将状态改为resolved
if(deferred) { // 如果deffred不是undefined,已经有指向函数的引用
handle(deferred); // 将deffred传给handle函数处理
}
}
/*
* 当用户调用then函数时调用的处理函数
* 等等就解释
*/
function handle(onResolved) {
if(state === 'pending') { // 判断当前所处的状态,如果是padding状态,即没有被处理状态,还处于初始状态
deferred = onResolved; // 将onResolved回调函数保存下来,稍后执行
return; // padding状态就不执行下面的语句
}
onResolved(value); // 如果不是padding状态,调用onResolved,并把结果传给回调函数
}
/*
* 这个是我的疑问then了
* 这里涉及一个关键字this,涉及的内容有点多,先不说
* 看等号后面是一个function,所以它是一个函数,并且传了一个参数,是回调函数
* 从上面的的代码可以看出onResolved是一个回调函数
*/
this.then = function(onResolved) {
handle(onResolved); // 每当调用then这个函数,就将onResolved(例子中的fn2)传给handle函数
};
/*
* 上面全都是函数和变量的定义,只有这一行是执行函数
* 疑问1解决:在new Promise的时候就执行了传过来的回调函数fn1
* 疑问2解决:在调用fn1的时候传过去的回调函数是Promise的内部函数resolve
* 疑问3解决:有点绕,我写在代码外面
*/
fn(resolve);
}
复制代码
疑问三解决:
前两个疑问的解决请看fn
的注释
我们在用Promise对象调用then函数的时候给他传了fn2
,也就是onResolved
是指向fn2
的,然后onResolved
又传给了handle
,在handle
里面调用了onResolved
,也就是说,fn2
在handle
里调用。
现在我们知道了fn2
是在handle
里被调用了。然后看handle
在哪里被调用:
因为例子中没有异步操作,所以先不考虑padding情况:
handle
函数在then
函数中调用
再看回最简单的Promise用法:
let fn1 = function (resolve) {
resolve(42)
}
let fn2 = function (result) {
console.log(result)
}
new Promise(fn1).then(fn2) // 42
复制代码
1. 执行new Promise(fn1)
时做了什么?
当程序执行到
new Promise
时,Promise
里面的语句fn(resolve)
会马上执行,调用了我们传过去的回调函数fn1
,并且给我们传回一个值叫resolve,这个值恰好又是一个回调函数(参数名resolve的指向是Promise
对象中的resolve()
函数,我们在这个例子中没有写任何的异步操作,所以东西几乎都是同步执行的)。我们又马上调用了resolve
回调函数,并给它传入一个值42(newValue),state
被置为'resolved',此时new Promise
就全部执行完成。总结一下上面这段话:我们这个例子没有异步操作,所以resolve
函数在new Promise
的时候就被调用了完了,状态改成了'resolved'
2. 执行then(fn2)
时做了什么?
例子中创建完
Promise
对象之后马上调用了then
函数,并且传入了一个会调函数fn2
(这里的then
其实是一个微任务,这里先不做讨论,暂且当它是马上执行的)。调用then
时,then
调用了Promise
对象的函数handle
并且把onResolved指向了fn2
,因为在new Promise
的时候就已经把status
改成'resolved'了,所以handle
函数体里面的if
语句不成立,于是就执行onResolved(value)
并且把new Promise
中的到的值传入,也就是说fn2
被handle
回调了。总结上面的这段话:在我们调用then
的时候调用了handle
,并且把new Promise
时得到的值传回给fn2回调函数
到现在疑问三解决了。
总结疑问三:
fn
被调用的时候,给fn1
传回了Promise
的内部函数resolve
,被fn1
的resolve参数所接收,所以在fn1
方法体内调用的resolve
实际是指向Promise
的内部函数resolve
,并未我们给他传了42这个值,被Promise
的resolve
函数保存在value当中。在调用then
函数的时候,value被handle
传出来给fn2
。
终于解决完问题了!!
关于Promise的三种状态的作用,还有如何实现串行的Promise,和reject,看完那篇参考文章的还是很好理解的,里面还有很多东西,我就不写了,去看吧。
四、Promise 的使用和理解中涉及到的知识点
- new 关键字创建对象
- this关键字
- 原型链
- js和浏览器的异步机制
- event loop