最近新型冠状病毒严重,在家也没啥事,写写文章,Promise 前端开发或多或少都有了解或使用到,我也经常用到。 希望阅读本章能帮助了解Promise到底是怎么实现的,或多或少能帮你^_^
为什么出现Promise
在javascript开发过程中,代码是单线程执行的,同步操作,彼此之间不会等待,这可以说是它的优势,但是也有它的弊端,如一些网络操作,浏览器事件,文件等操作等,都必须异步执行,针对这些情况,起初的操作都是使用回调函数实现。
实现方式如下(伪代码):
function One(callback) {
if (success) {
callback(err, result);
} else {
callback(err, null);
}
}
One(function (err, result) {
//执行完One函数内的内容,成功的结果回调回来向下执行
})
上述代码只是一层级回调,如果代码复杂后,会出现多层级的回调,代码可读性也会很差,那有没有一种方式,不用考虑里面的内容,直接根据结果成功还是失败执行下面的代码呢?有的,Promise(承诺),在ES6中对Promise进行了统一的规范
什么是promise
Promise可能大家都不陌生,因为Promise规范已经出来好一段时间了,同时Promise也已经纳入了ES6,而且高版本的chrome、firefox浏览器都已经原生实现了Promise,只不过和现如今流行的类Promise类库相比少些API。
Promise规范如下:
- 一个promise可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)
- 一个promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换
- promise必须实现
then
方法(可以说,then就是promise的核心),而且then必须返回一个promise,同一个promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致 - then方法接受两个参数,第一个参数是成功时的回调,在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在promise由“等待”态转换到“拒绝”态时调用。同时,then可以接受另一个promise传入,也接受一个“类then”的对象或方法,即thenable对象。
Promise原理与讲解
原理剖析--极简promise
由以上规范就容易就能实现这个类的大致结构
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Promise</title>
</head>
<body>
<script type="text/javascript">
class Promise {
callbacks = [];
constructor(fn) {
fn(this.resolve.bind(this));
}
then(onFulfilled) {
this.callbacks.push(onFulfilled);
}
resolve(value) {
this.callbacks.forEach(fn => fn(value));
}
}
//Promise应用
let p = new Promise(resolve => {
setTimeout(() => {
resolve('测试');
}, 2000);
})
p.then((tip)=>{
console.log('tip1',tip) // tip1,测试
})
p.then((tip)=>{
console.log('tip2',tip) // tip2,测试
})
</script>
</body>
</html>
这个简单版本大致逻辑是:
实例化Promise时,其类构造函数初始化执行了回调函数,并将绑定了当前实例的resolve方法作为参数回传给回到函数。接着调动Promise对象的then方法, 注册异步操作完成后的回调函数。 当异步操作完成时,调用resolve方法, 该方法执行then方法注册的回调函数。
这里then 方法注册完成时的回到是一个数组, then方法可以多次调用。注册的函数会在异步操作完成后根据添加的顺序依次执行。
相信仔细的人应该可以看出来,then
方法应该能够链式调用,但是上面的最基础简单的版本显然无法支持链式调用。想让then
方法支持链式调用,其实也是很简单的(如果写过jQuery插件的同学应该熟悉):
// 在上方代码添加
then(onFulfilled) {
this.callbacks.push(onFulfilled);
return this; //看这里
}
// 修改其调用方式
p.then((tip)=>{
console.log('tip1',tip) // tip1,测试
}).then((tip)=>{
console.log('tip2',tip) // tip2,测试
})
加入延时机制
首先我们吧上方代码的栗子中 setTimeout 去掉,在执行一下
//Promise应用
let p = new Promise(resolve => {
console.log('同步执行1');
resolve('同步执行2');
}).then(tip => {
console.log('then1', tip);
}).then(tip => {
console.log('then2', tip);
});
发现只打印了 同步执行1
这显然是不允许的,Promises/A+
规范明确要求回调需要通过异步方式执行,用以保证一致可靠的执行顺序。因此我们要加入一些处理,保证在resolve
执行之前,then
方法已经注册完所有的回调。我们可以这样改造下resolve
函数
// 修改上方代码
resolve(value) {
//看这里
setTimeout(() => {
this.callbacks.forEach(fn => fn(value));
});
}
//通过`setTimeout`机制,将`resolve`中执行回调的逻辑放置到`JS`任务队列末尾,以保证在`resolve`执行时,`then`方法的回调函数已经注册完成.
加入状态
为了解决上面提到的问题, 我们需要加入状态机制, 也就是大家熟知的pending, fulfilled, rejected。
Promises/A+ 规范中明确规定了, pending 可以转化为fulfilled或rejected并且只能转化一次。 也就是说如果pending转为fulfiled就不能再转化到rejected。 并且fulfilled 和 rejected状态只能pending转化而来, 两者之间不能互相转换。
// 修改如下
class Promise {
callbacks = [];
state = 'pending';//增加状态
value = null;//保存结果
constructor(fn) {
fn(this.resolve.bind(this));
}
then(onFulfilled) {
if (this.state === 'pending') {
//在resolve之前,跟之前逻辑一样,添加到callbacks中
this.callbacks.push(onFulfilled);
} else {
//在resolve之后,直接执行回调,返回结果了
onFulfilled(this.value);
}
return this;
}
resolve(value) {
this.state = 'fulfilled';//改变状态
this.value = value;//保存结果
this.callbacks.forEach(fn => fn(value));
}
}
resolve 执行时, 会将状态设置为 fulfilled , 并把 value 的值存起来, 在此之后调用 then 添加的新回调都会立即执行, 直接返回保存的value值
有同学发现增加了状态的后代码把setTimeout去掉了,原因是:resolve
执行时,会将状态设置为fulfilled
,在此之后调用then
添加的新回调,都会立即执行
链式Promise
那么这里问题又来了,如果用户再then函数里面注册的仍然是一个Promise
,该如何解决?比如下面
p()
.then(()=>{
// 这里返回Promise
return new Promise(function (resolve) {
resolve(resolve);
});
})
.then(function (res) {
// res拿到上一个Promise.resolve的值
});
真正的链式调用
真正的链式Promise是指在当前Promise达到fulfilled状态后, 即开始进行下一个Promise(后邻Promise)。 那么我们如何衔接当前Promise和后邻Promise呢,这个是重点,修改较多,我附一段完整的代码
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Promise</title>
</head>
<body>
<script type="text/javascript">
class Promise {
callbacks = [];
state = 'pending';//增加状态
value = null;//保存结果
constructor(fn) {
fn(this.resolve.bind(this));
}
then(onFulfilled) {
return new Promise(resolve => {
this.handle({
onFulfilled: onFulfilled || null,
resolve: resolve
});
});
}
handle(callback) {
if (this.state === 'pending') {
this.callbacks.push(callback);
return false;
}
//如果then中没有传递任何东西
if (!callback.onFulfilled) {
callback.resolve(this.value);
return false;
}
var ret = callback.onFulfilled(this.value);
callback.resolve(ret);
}
resolve(value) {
if (value && (typeof value === 'object' || typeof value === 'function')) {
var then = value.then;
if (typeof then === 'function') {
then.call(value, this.resolve.bind(this));
return;
}
}
this.state = 'fulfilled';//改变状态
this.value = value;//保存结果
this.callbacks.forEach(fn => fn(value));
}
}
// then中返回Promise
let p = new Promise(resolve => {
console.log('同步执行1');
resolve('同步执行2');
}).then(tip => {
console.log(tip);
return new Promise((resolve)=>{
resolve('同步执行3')
})
}).then(tip => {
console.log(tip);
});
</script>
</body>
</html>
-
then
方法中,创建并返回了新的Promise
实例,这是串行Promise
的基础,并且支持链式调用。 -
handle
方法是promise
内部的方法。then
方法传入的形参onFulfilled
以及创建新Promise
实例时传入的resolve
均被push
到当前promise
的callbacks
队列中,这是衔接当前promise
和后邻promise
的关键所在(这里一定要好好的分析下handle的作用)。 - resolve 方法中会先检查value是不是 Promise 对象, 如果是一个新的Promise, 那么先不改变当前 promise 的状态。
错误处理
在异常操作失败时,标记其状态为rejected, 并执行注册的失败回调
<!--完整代码-->
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Promise</title>
</head>
<body>
<script type="text/javascript">
class Promise {
callbacks = [];
state = 'pending';//增加状态
value = null;//保存结果
constructor(fn) {
fn(this.resolve.bind(this), this.reject.bind(this));
}
then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
this.handle({
onFulfilled: onFulfilled || null,
onRejected: onRejected || null,
resolve: resolve,
reject: reject
});
});
}
handle(callback) {
if (this.state === 'pending') {
this.callbacks.push(callback);
return;
}
let cb = this.state === 'fulfilled' ? callback.onFulfilled : callback.onRejected;
if (!cb) {//如果then中没有传递任何东西
cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
cb(this.value);
return;
}
// 这里处理,如果在执行成功回调、失败回调时代码出错怎么办,对于类似异常, 处理也很简单, 可以使用try-catch捕获错误, 然后将相应的promise状态设置为rejected状态
let ret;
try {
ret = cb(this.value);
cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
} catch (error) {
ret = error;
cb = callback.reject
} finally {
cb(ret);
}
}
resolve(value) {
if (value && (typeof value === 'object' || typeof value === 'function')) {
var then = value.then;
if (typeof then === 'function') {
then.call(value, this.resolve.bind(this), this.reject.bind(this));
return;
}
}
this.state = 'fulfilled';//改变状态
this.value = value;//保存结果
this.callbacks.forEach(callback => this.handle(callback));
}
reject(error) {
this.state = 'rejected';
this.value = error;
this.callbacks.forEach(callback => this.handle(callback));
}
}
//Promise应用
let p = new Promise(resolve => {
console.log('同步执行1');
resolve('同步执行2');
}).then(tip => {
return new Promise((resolve,reject)=>{
// 做个随机数控制resolve或者reject的调用
if(parseInt(Math.random()*10) > 4){
resolve(tip+' 成功')
}else{
reject(tip+' 失败')
}
})
}).then(result => {
console.log(result);
}, error => {
console.log(error);
});
</script>
</body>
</html>
总结
promise 里面的 then 函数仅仅是注册了后续需要执行的回调函数,同时返回一个新的Promise对象,以延续链式调用,真正的逻辑是在handle里面
对于内部 pending 、fulfilled 和 rejected 的状态转变,通过 handler 触发 resolve 和 reject 方法,然后更改state状态值
参考文章
MPromise
分层解析 Promise 的实现原理
Promise 原理解析 (渐进实例详解)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。