promise 机制
关于前端架构只是体系中的知识剩下的部分一直没有不全,不是不想写,实在是不敢轻易下笔,如果深度不够,我想关于es6的只是点我们都可以参考 阮一峰的es6-promise教程, 如果需要一定的深度,不仅仅需要查找一些资料,更重要的是还要吸收而后进一步整理。所以跟新完善的进展有点慢,但是,要么不做,要么就做好。我相信当您阅读完本文,关于 promise 相信您会有一个全新的认识。
温馨提示:
本文适合对es6是相对熟悉的看官阅读,如果您对es6还不是那么了解,建议先看完 阮一峰老师es6-promise教程 后再阅读本文
为了让您读起来不枯燥,咱们一边看题,一边思考,一边完善
- 先看最基础的使用
new Promise((resolve,reject)=>{
// resolve()
// reject();
})
分析: 通过上面代码不难看书,Promise 就是一个构造函数,并且接受两个方法作为参数
我们的雏形代码如下:
Promise1 function(excutor) {
function resolve(value){}
function reject(value) {}
exctuor(resolve, reject)
}
- 官方文档告诉我们说(1)对象的状态不受外界影响。(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果
var p1 = new Promise(function(resolve, reject) {
resolve(1)
throw Error('sync error')
})
.then(res => {
console.log(11111,res) // 11111 1
}, (error) => {console.log(error)})
.catch(err => {
console.log(err)
})
// 控制台输出 11111 1
分析:结合官方的说法和实际代码的运行结果,我们猜测,状态不仅仅是内部私有的,而且改变之后不可逆,根据 es6 中的说法三种状态分别为 pending(进行中)、fulfilled(已成功)和rejected(已失败)
进一步改善我们的代码
Promise1 function(excutor) {
let self = this; // 保持良好的习惯,避免一些不要的坑
self.status = 'pending'; // 初始状态为pending
self.value = null; // 返回值默认为null
self.reason = null; // 错误信息默认为null
function resolve(value){
// 不可逆操作
if (self.status === 'pending') {
self.status = 'fulfilled';
self.value = value;
}
}
function reject(reason) {
// 不可逆操作
if (self.status === 'pending') {
self.status = 'rejected'
self.reason = reason;
}
}
// 为了避免 resolve 和 reject 方法本身出错,我们容错一下
try {
exctuor(resolve, reject)
}catch(err) {
reject(err) // 通过reject来记录错误信息
}
}
Promise1.prototype.then = function (onFulfilled, onRejected) {
let self = this;
// then 方法后面的成功的回调
if (self.status === 'fulfilled') {
try{
onFulfilled(self.value) // 返回值
}catch(error) {
self.reject(error)
}
}
// then 方法后面的失败的回调
if (self.status === 'rejected') {
try{
onRejected(self.value) // 返回值
}catch(error) {
self.reject(error)
}
}
if (self.status === 'pending') {
}
}
- Promise 是异步编程的一种解决方案
看代码
new Promise(function(resolve, reject) {
//使用定时器来模拟异步
setTimeout(() => {
resolve(100);
}, 1000);
}).then(function(value) {
console.log(11111111, value);
});
// 一秒之后,控制台仍然答应出了 11111111 100
但是根据我们上面的实现1000之后,整个函数体内的上下就消失了,那么这个在设计上显然不合理,怎么办呢?那我们就先保存一下,等到整个运行结束再来执行,代码进一步完善如下:
Promise1 function(excutor) {
let self = this; // 保持良好的习惯,避免一些不要的坑
self.status = 'pending'; // 初始状态为pending
self.value = null; // 返回值默认为null
self.reason = null; // 错误信息默认为null
self.onFulfilledCallbacks = []; // 成功回调的队列
self.onRejectedCallbacks = []; // 失败回调的队列
function resolve(value){
// 不可逆操作
if (self.status === 'pending') {
self.status = 'fulfilled';
self.value = value;
}
}
function reject(reason) {
// 不可逆操作
if (self.status === 'pending') {
self.status = 'rejected'
self.reason = reason;
}
}
// 为了避免 resolve 和 reject 方法本身出错,我们容错一下
try {
exctuor(resolve, reject)
}catch(err) {
reject(err) // 通过reject来记录错误信息
}
}
Promise1.prototype.then = function (onFulfilled, onRejected) {
let self = this;
// then 方法后面的成功的回调
if (self.status === 'fulfilled') {
try{
onFulfilled(self.value) // 返回值
}catch(error) {
self.reject(error)
}
}
// then 方法后面的失败的回调
if (self.status === 'rejected') {
try{
onRejected(self.reason) // 返回值
}catch(error) {
self.reject(error)
}
}
// 当状态处于pending状态时,像队列中添加各自的回调,然后在同一执行
if (self.status === 'pending') {
self.onFulfilledCallbacks.push(() => {
try {
onFulfilled(self.value)
}catch(error){
self.reject(err);
}
})
self.onFulfilledCallbacks.push(() => {
try {
onRejected(self.reason)
}catch(error){
self.reject(err);
}
})
}
}
- 到目前为止,看起来基本的promise结构已经出来了,但是promise是可以链式调用的,所谓的链式调用的本质上就是每一个方法都返回一个新的promise 实例,这个就相对简单了,在我们所有的参数方法外面包一层 new Promise() 就好了
代码如下:
Promise1.prototype.then = function(onFulfilled, onRejected) {
let self = this;
if (self.status === "fulfilled") {
// 返回新的Promise
return new Promise1((resolve, reject) => {
try {
// 如果不指定错误的回调函数,会再次抛出错误,
let x = onFulfilled(self.value);
} catch (err) {
reject(err);
}
});
}
if (self.status === "rejected") {
return new Promise1((resolve, reject) => {
try {
let x = onRejected(self.reason);
} catch (err) {
reject(err);
}
});
}
if (self.status === "pending") {
return new Promise1((resolve, reject) => {
self.onFulfilledCallbacks.push(() => {
let x = onFulfilled(self.value);
});
self.onRejectedCallbacks.push(() => {
let x = onRejected(self.reason);
});
});
}
- 听所then的回调函数还可以继续返回promise
new Promise(function(resolve, reject) {
// TODO
resolve(11111);
}).then(res => {
return new Promise(function(resolve, reject) {
resolve(res);
}).then(res => {
console.log(2222, res);
});
});
// 控制台输出 2222 11111
这样的话,我们上面的代码还不够,的进一步判断返回的是否是一个promise实例
代码如下:
Promise1.prototype.then = function(onFulfilled, onRejected) {
let self = this;
if (self.status === "fulfilled") {
// 返回新的Promise
return new Promise1((resolve, reject) => {
try {
// 如果不指定错误的回调函数,会再次抛出错误,
let x = onFulfilled(self.value);
// 针对返回做判断,如果是一个Promise对象,就继续 .then 保持整个函数的链式结构,否则就调用 resolve 方法
x instanceof Promise1 && x.then(resolve, reject) : resolve(x)
} catch (err) {
reject(err);
}
});
}
if (self.status === "rejected") {
return new Promise1((resolve, reject) => {
try {
let x = onRejected(self.reason);
x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
} catch (err) {
reject(err);
}
});
}
if (self.status === "pending") {
return new Promise1((resolve, reject) => {
self.onFulfilledCallbacks.push(() => {
let x = onFulfilled(self.value);
x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
});
self.onRejectedCallbacks.push(() => {
let x = onRejected(self.reason);
x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
});
});
}
到目前为止,整个 Promise 的代码 如下:
function Promise1(excutor) {
let self = this;
self.status = "pending";
self.value = null;
self.reason = null;
self.onFulfilledCallbacks = []; // 成功回调的队列
self.onRejectedCallbacks = []; // 失败回调的队列
// 什么时候调用,什么时候改变状态,且状态不可逆
function resolve(value) {
if (self.status === "pending") {
self.value = value;
self.status = "fulfilled";
// 以下方法会在先调用then回调之后,在resolve的时候执行
self.onFulfilledCallbacks.length && self.onFulfilledCallbacks.forEach(item => item());
}
}
function reject(reason) {
if (self.status === "pending") {
self.reason = reason;
self.status = "rejected";
self.onFulfilledCallbacks.length && self.onRejectedCallbacks.forEach(item => item());
}
}
// 这里可以捕获一些不必要的错误,eg: excute 不是一个 funciton,或者 excute 内部出错
try {
excutor(resolve, reject); // 构造函数传进来的函数
} catch (err) {
reject(err);
}
}
Promise1.prototype.then = function(onFulfilled, onRejected) {
let self = this;
if (self.status === "fulfilled") {
// 返回新的Promise
return new Promise1((resolve, reject) => {
try {
let x = onFulfilled(self.value);
// then 的回调也有可以返回一个Promise
x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
} catch (err) {
reject(err);
}
});
}
if (self.status === "rejected") {
return new Promise1((resolve, reject) => {
try {
let x = onRejected(self.reason); // 如果不指定错误的回调函数,会再次抛出错误,
x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
} catch (err) {
reject(err);
}
});
}
if (self.status === "pending") {
return new Promise1((resolve, reject) => {
self.onFulfilledCallbacks.push(() => {
let x = onFulfilled(self.value);
x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
});
self.onRejectedCallbacks.push(() => {
let x = onRejected(self.reason);
x instanceof Promise1 ? x.then(resolve, reject) : resolve(x);
});
});
}
};
- 此时此刻官方又告诉我们说 Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数 。
这就帅歪歪了啊,啥都不用谢直接复制就好了
promise 添加 catch 方法:
Promise1.prototype.catch = function(errorCallBack) {
return this.then(null, errorCallBack);
};
另外的两个方法也直接附上
Promise1.reject = function(reason) {
return new Promise1((resolve, reject) => {
reject(reason);
});
};
Promise1.resolve = function(value) {
return new Promise1((resolve, reject) => {
resolve(value);
});
};
Promise.prototype.finally = function(callback) {
let self = this.constructor;
return self.then(
value => self.resolve(callback()).then(() => value),
reason =>
self.resolve(callback()).then(() => {
throw reason;
})
);
};
到此为止整个 手动 撸 Promise 的过程全部结束。当然相信你对 promise 有了更加深刻的理解
接下来上一组 promise 相关的面试题来热热身,促进一下消化
思考一下一面三组代码的输出结果
promise 捕获错误:
🌰1:
new Promise(function(resolve, reject) {
throw Error('sync error')
})
.then(res => {
console.log(res)
})
.catch(err => {
console.log(err)
})
🌰2:
new Promise(function(resolve, reject) {
setTimeout(() => {
throw Error('async error')
})
})
.then(res => {
console.log(res)
})
.catch(err => {
console.log(err)
})
🌰3:
new Promise(function(resolve, reject) {
resolve()
})
.then(res => {
throw Error('sync error')
})
正确答案为:
- 第一组错误被catch 捕获,控制台输出错误信息 Error: sync error
- 第二组的定时器抛出错误的时候,catch 已失效,无法正确捕获错误,控制台直接报错
- 第三组抛出错误信息在promise内部,但是由于没有被catch捕获,直接抛出到外部,控制台直接报错
将上面的代码稍作修改,再来一次
promise 状态一旦发生改变不可逆转:
🌰1:
new Promise(function(resolve, reject) {
resolve(1)
throw Error('sync error')
})
.then(res => {
console.log(res)
})
.catch(err => {
console.log(err)
})
🌰2:
new Promise(function(resolve, reject) {
reject(2)
resolve(1)
})
.then(res => {
console.log(res)
})
.catch(err => {
console.log(err)
})
🌰3:
new Promise(function(resolve, reject) {
resolve(1)
})
.then(res => {
throw Error('sync error')
console.log(res)
})
.catch(err => {
console.log(err)
})
正确答案为:
- 第一组,控制台输出1, 一旦状态发生改变不可逆转,后面的代码得不到执行
- 第二组,控制台输出2, 原因同上
- 第三组,then 内部抛出的错误被外部捕获,控制台打印出错误信息
咱们继续,还不够爽
promise 中的回调是异步的:
new Promise(function(resolve, reject) {
resolve()
setTimeout(() => {
console.log(1)
})
console.log(2)
})
.then(res => {
console.log(3)
})
console.log(4)
答案: 1432
new promise 是属于构造函数,会立即执行,控制台先输出3
setTimeout 延时,在空闲是才会执行 最后输出 1
then 方法也是会在整个promise 的回调结束之后执行,而且是一个异步的,所以会处于等待状态,代码继续向下执行, 控制台打印 4 ,而后再返回来执行then中的回调,输出 3
promise 返回一个新的promise(链式调用):
new Promise(function(resolve, reject) {
reject(1)
})
.then(res => {
console.log(res)
return 2
},err => {
console.log(err) // 输出 1
return 3
}
)
.catch(err => {
// 有err回调,这一步跳过
console.log(err)
return 4
})
.finally(res => {
// 该函数内部的代码执行和状态以及返回值无关
console.log(res) // 直接返回 undefined 参见 Promise.prototype.finally
return 5
})
.then(
res => console.log(res), // err 回调中返回了新的值为3,所以执行成功的回调
err => console.log(err)
)
输出结果:
- 1
- undefined
- 3
原因已经进行了说明,就不在解析
希望对各位看官有所帮助
其它前端性能优化:
- 图片优化——质量与性能的博弈
- 浏览器缓存机制介绍与缓存策略剖析
- webpack 性能调优与 Gzip 原理
- 本地存储——从 Cookie 到 Web Storage、IndexDB
- CDN 的缓存与回源机制解析
- 服务端渲染的探索与实践
- 解锁浏览器背后的运行机制
- DOM 优化原理与基本实践
- Event Loop 与异步更新策略
- 回流(Reflow)与重绘(Repaint)
- Lazy-Load
- 事件的节流(throttle)与防抖(debounce
- 前端学习资料下载
- 技术体系分类
前端技术架构体系(没有链接的后续跟进):
- 调用堆栈
- 作用域闭包
- this全面解析
- 深浅拷贝的原理
- 原型prototype
- 事件机制、
- Event Loop
- Promise机制
- async / await原理、
- 防抖/节流原理
- 模块化详解、
- es6重难点、
- 浏览器薰染原理
- webpack配置(原理)
- 前端监控、
- 跨域和安全、
- 性能优化
- VirtualDom原理、
- Diff算法、
- 数据的双向绑定
- [TCP协议(三次握手、四次挥手)](https://blog.csdn.net/woleigequshawanyier/article/details/85223642
- DNS域名解析
其它相关
欢迎各位看官的批评和指正,共同学习和成长
希望该文章对您有帮助,你的 支持和鼓励会是我持续的动力