Promise原理 渐进渐知
一、极简promise雏形
function Promise(fn) {
var value = null,
callbacks = []; //callbacks为数组,因为可能同时有很多个回调
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
return this;
};
function resolve(value) {
callbacks.forEach(function (callback) {
callback(value);
});
}
fn(resolve);
}
下面应用一下这个手写的Promise基本原理:
let p = new Promise(resolve => {
setTimeout(() => {
console.log('done');
resolve('5秒');
}, 5000);
}).then((tip) => {
console.log(tip);
})
运行结果如下:
约5秒钟后出现“done 5秒钟”

下面运用调试功能,看一下基本的运行逻辑:
在
let p = new Promise......
这一行上面打断点,调试后代码运行在此行上,点“步入”,进入构造函数
function Promise(fn)

callbacks数组为“undefined”

从第5步直接跳转至第6步,如下:

第7步之后跳到第8步,完成p实例化;第9步开始调用.then函数

从第11步可以看出,
(tip) => {
console.log(tip);
已经压栈进入callbacks数组中。

第12步完成.then函数的调用,第13步进入setTimeout异步函数的调用;

第14步,输出了“done”

第15步,进入resolve函数

第16步,value值为“5秒"

第17步,相当于第16步时,callback(value)调用的是:
(value) => {
console.log(value);

第18步,
(tip) => {
console.log(tip);
}
函数调试结束

第19步,forEach函数调用结束 ;

第20步,resolve函数调用结束 ;

第21步,setTimout函数调用结束 ;
至此,调试完毕!

综合上述:
上述代码很简单,大致的逻辑是这样的:
- 调用
then方法,将想要在Promise异步操作成功时执行的回调放入callbacks队列,其实也就是注册回调函数,可以向观察者模式方向思考; - 创建
Promise实例时传入的函数会被赋予一个函数类型的参数,即resolve,它接收一个参数value,代表异步操作返回的结果,当一步操作执行成功后,用户会调用resolve方法,这时候其实真正执行的操作是将callbacks队列中的回调一一执行;
首先 new Promise 时,传给 Promise 的函数设置定时器模拟异步的场景,因为setTimeout为异步,没有立即执行,而是紧接着调用 Promise 对象的 then 方法;调用then方法注册异步操作完成后的 onFulfilled(这一步也就相当于将匿名函数压入callbacks数组栈中);然后,又开始执行setTimeout异步函数,在异步函数setTimeout中,调用 resolve(value), 执行 then 方法注册的 onFulfilled。
then 方法注册的 onFulfilled 是存在一个数组中,可见 then 方法可以调用多次,注册的多个onFulfilled 会在异步操作完成后根据添加的顺序依次执行。如下:
function Promise(fn) {
var value = null,
callbacks = []; //callbacks为数组,因为可能同时有很多个回调
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
// return this;
};
function resolve(value) {
callbacks.forEach(function (callback) {
callback(value);
});
}
fn(resolve);
}
let p = new Promise(resolve => {
setTimeout(() => {
console.log('done');
resolve('5秒');
}, 5000);
})
p.then((tip) => {
console.log("then1",tip);
})
p.then((tip) => {
console.log("then2",tip);
})
运行结果如下:

上例中,要先定义一个变量 p ,然后 p.then 两次。而规范中要求,then 方法应该能够链式调用。实现也简单,只需要在 then 中添加 return this 即可。如下所示:
function Promise(fn) {
var value = null,
callbacks = []; //callbacks为数组,因为可能同时有很多个回调
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
return this; //this 指向调用这个方法的对象实例;
};
function resolve(value) {
callbacks.forEach(function (callback) {
callback(value);
});
}
fn(resolve);
}
let p = new Promise(resolve => {
setTimeout(() => {
console.log('done');
resolve('5秒');
}, 5000);
}).then((tip) => {
console.log("then1",tip);
}).then((tip) => {
console.log("then2",tip);
})
运行结果如下:

二、加入延时机制
上面 Promise 的实现存在一个问题:如果在 then 方法注册 onFulfilled 之前,resolve 就执行了,onFulfilled 就不会执行到了。比如上面的例子中我们把 setTimout 去掉:
function Promise(fn) {
var value = null,
callbacks = []; //callbacks为数组,因为可能同时有很多个回调
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
return this; //this 指向调用这个方法的对象实例;
};
function resolve(value) {
callbacks.forEach(function (callback) {
callback(value);
});
}
fn(resolve);
}
let p = new Promise(resolve =>{
console.log('同步执行1');
resolve('同步执行2');
}).then((tip) => {
console.log("then1",tip);
}).then((tip) => {
console.log("then2",tip);
})
运行结果如下

执行结果显示,只有 "同步执行1" 被打印了出来,后面的”同步执行2“ 、"then1" 和 "then2" 均没有打印出来。再回去看下 Promise 的源码,也很好理解,在调用resolve函数 执行时,callbacks 还是空数组,还没有onFulfilled 注册上来,所以resolve、then1、then2都没有执行。
在前面的调试中已经看到,是先执行.then函数将其中的匿名函数注册到callbacks数组中,再调用的setTimeout函数中的resolve,因为callbacks数组中已经压入了匿名函数,所以resolve函数可以调用并执行。纵观整个Promise,执行顺序为then->setTimeout->resolve,这样就保证了Promise的顺利执行。
Promises/A+规范明确要求回调需要通过异步方式执行,用以保证一致可靠的执行顺序。因此要加入一些处理,保证在 resolve 执行之前,then 方法已经注册完所有的回调:
function Promise(fn) {
var value = null,
callbacks = []; //callbacks为数组,因为可能同时有很多个回调
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
return this; //this 指向调用这个方法的对象实例;
};
//改写resolve函数,增加setTimeout;
function resolve(value) {
setTimeout(function() {
callbacks.forEach(function (callback) {
callback(value);
});
}, 0)}
fn(resolve);
}
let p = new Promise(resolve =>{
console.log('同步执行1');
resolve('同步执行2');
}).then((tip) => {
console.log("then1",tip);
}).then((tip) => {
console.log("then2",tip);
})
运行结果如下

上述代码中,我们在 resolve 中增加定时器,通过 setTimeout 机制,将 resolve 中执行回调的逻辑放置到JS任务队列末尾,以保证先执行then方法的 onFulfilled注册完成,再执行resolve函数(注:这时callbacks数组中已经压入then1和then2两个匿名函数,resolve函数就可以调用并使用了)。
但是这样依然存在问题,在 resolve 执行后,再通过 then 注册上来的 onFulfilled 都没有机会执行了。如下所示,我们加了延迟后,then1 和 then2 可以打印出来了,但下例中的 then3 依然打印不出来。所以我们需要增加状态,并且保存 resolve 的值。
function Promise(fn) {
var value = null,
callbacks = []; //callbacks为数组,因为可能同时有很多个回调
this.then = function (onFulfilled) {
callbacks.push(onFulfilled);
return this; //this 指向调用这个方法的对象实例;
};
//改写resolve函数,增加setTimeout;
function resolve(value) {
setTimeout(function() {
callbacks.forEach(function (callback) {
callback(value);
});
}, 0)}
fn(resolve);
}
let p = new Promise(resolve =>{
console.log('同步执行1');
resolve('同步执行2');
}).then((tip) => {
console.log("then1",tip);
}).then((tip) => {
console.log("then2",tip);
})
setTimeout(() => {
p.then(tip => {
console.log('then3', tip);
})
});
运行结果如下

三、增加状态
为了解决上一节抛出的问题,我们必须加入状态机制,也就是大家熟知的pending、fulfilled、rejected。
Promises/A+规范中的2.1Promise States中明确规定了,pending可以转化为fulfilled或rejected并且只能转化一次,也就是说如果pending转化到fulfilled状态,那么就不能再转化到rejected。并且fulfilled和rejected状态只能由pending转化而来,两者之间不能互相转换。如下图所示:

增加状态后的Promise如下
function Promise(fn) {
var state = 'pending',
value = null,
callbacks = [];
this.then = function (onFulfilled) {
if (state === 'pending') {
callbacks.push(onFulfilled);
return this;
}
onFulfilled(value);
return this;
};
function resolve(newValue) {
value = newValue;
state = 'fulfilled';
setTimeout(function () {
callbacks.forEach(function (callback) {
callback(value);
});
}, 0);
}
fn(resolve);
}
let p = new Promise((resolve,reject) => {
setTimeout(()=>{
resolve('morrain');
},1000)
}).then(value => {
console.log('value=',value);
}).then(value => {
console.log('value=',value);
})
运行结果如下

resolve 执行时,会将状态设置为 fulfilled ,并把 value 的值存起来,在此之后调用 then 添加的新回调,都会立即执行,直接返回保存的value值。
增加代码:
setTimeout(() => {
p.then(tip => {
console.log('then3', tip);
})
});
改造代码如下
function Promise(fn) {
var state = 'pending',
value = null,
callbacks = [];
this.then = function (onFulfilled) {
if (state === 'pending') {
callbacks.push(onFulfilled);
return this;
}
onFulfilled(value);
return this;
};
function resolve(newValue) {
value = newValue;
state = 'fulfilled';
setTimeout(function () {
callbacks.forEach(function (callback) {
callback(value);
});
}, 0);
}
fn(resolve);
}
let p = new Promise((resolve,reject) => {
setTimeout(()=>{
resolve('morrain');
},1000)
}).then(value => {
console.log('value=',value);
}).then(value => {
console.log('value=',value);
})
//改造增加的代码如下:
setTimeout(() => {
p.then(tip => {
console.log('then3', tip);
})
});
运行结果如下
从上述代码的调试可以看到,增加的代码
setTimeout(() => {
p.then(tip => {
console.log('then3', tip);
})
});
此时的state已经是fulfilled状态了,所以,走的是
onFulfilled(value);
return this;
这个分支并执行的。
至此,一个初具功能的Promise就实现好了,它实现了 then,实现了链式调用,实现了状态管理等等。但仔细想想,链式调用的实现只是在 then 中 return 了 this,因为是同一个实例,调用再多次 then 也只能返回相同的一个结果,这显然是不能满足我们的要求的。下面,讲述如何实现真正的链式调用。
三、链式调用
通常,我们希望的链式调用是如下这样的:
const p1 = Promise.resolve(42);
const p2 = p1.then(value => value + 1); // 返回 43
p2.then(console.log); // 输出: 43
每个 then 注册的 onFulfilled 都返回了不同的结果,层层递进,很明显在 then 方法中 return this 不能达到这个效果。引入真正的链式调用,then 返回的一定是一个新的Promise实例。
真正的链式 Promise 是指在当前 Promise 达到 fulfilled 状态后,即开始进行下一个 Promise(后邻 Promise)。那么我们如何衔接当前 Promise 和后邻 Promise 呢?(这是理解 Promise 的难点)
链式调用的实现
就是在then方法里面return一个promise就好啦。
function Promise(fn) {
var state = 'pending',
value = null,
callbacks = [];
this.then = function (onFulfilled) {
return new Promise(function (resolve) {
handle({
onFulfilled: onFulfilled || null,
resolve: resolve
});
});
};
function handle(callback) {
if (state === 'pending') {
callbacks.push(callback);
return;
}
//如果then中没有传递任何东西
if(!callback.onFulfilled) {
callback.resolve(value);
return;
}
var ret = callback.onFulfilled(value);
callback.resolve(ret);
}
function resolve(newValue) {
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
var then = newValue.then;
if (typeof then === 'function') {
then.call(newValue, resolve);
return;
}
}
state = 'fulfilled';
value = newValue;
setTimeout(function () {
callbacks.forEach(function (callback) {
handle(callback);
});
}, 0);
}
fn(resolve);
}
let p = new Promise((resolve,reject) => {
setTimeout(()=>{
resolve(42);
},1000)
}).then(
value => value + 1
).then(console.log)
上述代码的大体逻辑与前面的测试截图基本是一致的,这里面最大的难点就是理解
this.then = function (onFulfilled) {
return new Promise(function (resolve) {
handle({
onFulfilled: onFulfilled || null,
resolve: resolve
});
});
};
这一段代码中,new Promise实例中调用的handle函数为什么是上一个Promise实例的handle方法?
为了跟踪,我们在代码中加入验证相关代码,如下
// 用于生成Promise实例的唯一ID
let promiseId = 0;
function Promise(fn) {
// 给每个Promise实例添加唯一标识
this.id = ++promiseId;
var state = 'pending',
value = null,
callbacks = [];
// 打印:当前实例初始化,展示ID和空的callbacks
console.log(`[Promise ${this.id}] 初始化,callbacks初始值:`, callbacks);
this.then = function (onFulfilled) {
// 创建新的Promise实例,并打印关联信息
const newPromise = new Promise(function (resolve) {
handle({
onFulfilled: onFulfilled || null,
resolve: resolve
});
});
// 打印:当前实例调用then,返回新实例的ID
console.log(`[Promise ${this.id}] 调用then,返回新Promise ${newPromise.id}`);
return newPromise;
};
function handle(callback) {
if (state === 'pending') {
callbacks.push(callback);
// 打印:当前实例的callbacks新增回调,展示当前callbacks长度
console.log(`[Promise ${this.id}] callbacks新增回调,当前长度:`, callbacks.length);
return;
}
// 如果then中没有传递任何东西
if(!callback.onFulfilled) {
callback.resolve(value);
return;
}
var ret = callback.onFulfilled(value);
callback.resolve(ret);
}
function resolve(newValue) {
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
var then = newValue.then;
if (typeof then === 'function') {
then.call(newValue, resolve);
return;
}
}
state = 'fulfilled';
value = newValue;
// 打印:当前实例状态变为fulfilled,准备执行callbacks
console.log(`[Promise ${this.id}] 状态变为fulfilled,准备执行callbacks(长度:${callbacks.length})`);
setTimeout(function () {
callbacks.forEach(function (callback) {
handle(callback);
});
}, 0);
}
// 绑定this到handle和resolve,否则内部this指向window
handle = handle.bind(this);
resolve = resolve.bind(this);
fn(resolve);
}
// 测试代码
let p = new Promise((resolve,reject) => {
setTimeout(()=>{
console.log('执行resolve(42)');
resolve(42);
},1000)
}).then(
value => value + 1
).then(console.log)
在上述代码中
new Promise((resolve,reject) => {
setTimeout(()=>{
console.log('执行resolve(42)');
resolve(42);
},1000)
})
上面这段代码产生的Promise实例为p1
.then(
value => value + 1
)
上面这段代码,即第一个then函数产生的new Promise实例为p2
.then(console.log)
上面这段代码,产生的new Promise实例为p3
在下面位置处打断点,并开始调试

当调试从.then(value => value + 1)进入到this.then = function(onFullfilled)函数内部时,newPromise的id是2,但是在console.log显示当前的上下文环境还是id=1,也就是说,还是p1的实例环境。
通俗理解就是:newPromise函数内调用的handle是上一个Promise实例的handle。
彻底搞清楚为什么新 Promise 会关联到上一个 Promise 的 handle 函数,这是理解 Promise 实现本质的关键,下面进行具体解读:
核心前提:先明确两个基础概念
在分析前,先巩固两个底层概念,这是理解的基石:
- 词法作用域(静态作用域):函数的作用域在定义时就确定了,而非执行时。比如函数 A 定义在函数 B 内部,A 就能访问 B 的变量,不管 A 在哪里执行。也就是说,JavaScript 引擎在解析阶段(编译阶段)就确定了每个变量和函数的作用域。
- 作用域链:函数执行时,会先在自己的作用域找变量,找不到就沿着定义时的作用域链向上找,直到全局作用域。
从作用域视角拆解 Promise 执行逻辑
handle、then、resolve 等函数的词法作用域绑定,是 “新 Promise 关联上一个 handle” 的根本原因。我们结合作用域链,逐环节分析:1. Promise 构造函数的作用域结构(定义阶段)
// 全局作用域
let promiseId = 0;
function Promise(fn) { // 构造函数作用域(记为 Scope-P)
const self = this; // 指向当前 Promise 实例(如 P1、P2)
this.id = ++promiseId;
var state = 'pending',
value = null,
callbacks = [];
// -------------- then 函数:定义在 Scope-P 中 --------------
this.then = function (onFulfilled) { // then 函数作用域(记为 Scope-then)
// Scope-then 能访问外层的 Scope-P(self、state、callbacks、handle 等)
const nextPromise = new Promise(function (resolve) { // 新 Promise 的构造函数作用域(Scope-P2)
// 核心:这里调用的 handle 是外层 Scope-P 的 handle(上一个 Promise 的 handle)
handle({
onFulfilled: onFulfilled || null,
resolve: resolve
});
});
return nextPromise;
};
// -------------- handle 函数:定义在 Scope-P 中 --------------
function handle(callback) { // handle 函数作用域(Scope-handle)
// Scope-handle 能访问外层 Scope-P 的所有变量:self、state、value、callbacks
if (state === 'pending') {
callbacks.push(callback);
return;
}
if(!callback.onFulfilled) {
callback.resolve(value);
return;
}
var ret = callback.onFulfilled(value);
callback.resolve(ret);
}
// -------------- resolve 函数:定义在 Scope-P 中 --------------
function resolve(newValue) { // resolve 函数作用域(Scope-resolve)
// Scope-resolve 能访问外层 Scope-P 的所有变量:self、state、value、callbacks、handle
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
var then = newValue.then;
if (typeof then === 'function') {
then.call(newValue, resolve);
return;
}
}
state = 'fulfilled';
value = newValue;
setTimeout(function () {
// 这里调用的 handle 是 Scope-P 的 handle(当前 Promise 实例的 handle)
callbacks.forEach(function (callback) {
handle(callback);
});
}, 0);
}
resolve._owner = this;
fn(resolve);
}
2. 关键:then 函数中 handle 的作用域绑定(核心原因)
P1.then(...) 时,then 函数的执行逻辑如下(结合作用域链):// P1 是第一个 Promise 实例,其构造函数作用域是 Scope-P1
P1.then(function (value) { return value + 1 })
- Step 1:执行
P1.then,进入then函数的作用域(Scope-then1),这个作用域词法上绑定到 Scope-P1(因为then函数定义在 P1 的构造函数作用域里)。 - Step 2:在 Scope-then1 中创建新 Promise 实例 P2,执行 P2 的构造函数(Scope-P2),并传入一个匿名函数:
function (resolve) { // 这个函数定义在 Scope-then1 中,作用域链指向 Scope-then1 → Scope-P1 → 全局
handle({ onFulfilled: ..., resolve: resolve });
}
Step 3:执行这个匿名函数时,要找 handle 变量:
- 先在自己的作用域(匿名函数作用域)找 → 没有;
- 沿着作用域链向上找,到 Scope-then1 → 没有;
- 再向上到 Scope-P1(P1 的构造函数作用域)→ 找到
handle函数(P1 的 handle); - 因此,这里调用的
handle是P1 的 handle,而非 P2 的 handle。
then 函数内调用的 handle,通过词法作用域链,绑定到了定义 then 函数的那个 Promise 实例(上一个 Promise)的 handle,而非新创建的 Promise 实例的 handle。3. 回调执行阶段:作用域链的延续
resolve(42) 后,触发 callbacks.forEach(handle):callbacks是 Scope-P1 中的数组,里面存的是包含 P2 的resolve函数的回调对象;- 遍历执行
handle(callback)时,handle依然是 Scope-P1 中的函数(作用域绑定未变); - 在
handle中执行callback.resolve(ret)时,callback.resolve是 P2 的resolve函数(定义在 Scope-P2 中),但执行环境还是 Scope-P1 的handle作用域; - P2 的
resolve执行后,又会触发 P2 的handle(Scope-P2 中的handle),处理 P3 的resolve—— 作用域链层层传递,形成链式调用。
- 词法绑定是根本:
then函数定义在某个 Promise 实例(如 P1)的构造函数作用域内,因此then内部调用的handle会通过作用域链,绑定到该实例的handle(P1 的 handle),而非新创建的实例(P2)的handle。 - 作用域链传递链式调用:每个 Promise 实例的
handle作用域,会调用下一个 Promise 实例的resolve,而resolve又绑定到新实例的作用域,从而通过作用域链的层层传递,实现 Promise 的链式执行。 - 执行时作用域不变:函数的作用域在定义时确定,不管
handle或resolve在哪里执行,都会沿着定义时的作用域链找变量,这是 JavaScript 静态作用域的核心特征,也是 Promise 链式调用能实现的底层保障。
当第一个 Promise 成功时,resolve 方法将其状态置为 fulfilled ,并保存 resolve 带过来的value。然后取出 callbacks 中的对象,执行当前 Promise的 onFulfilled,返回值通过调用第二个 Promise 的 resolve 方法,传递给第二个 Promise。
经上思路参考如下文章:
https://mengera88.github.io/2017/05/18/Promise%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/

浙公网安备 33010602011771号