一、写在前面:为何需要Promise
关于Promise,首先咱们学习Promise之前一定要问问自己,这个玩意儿解决了什么问题,如下参考:
- 回调地狱
在ES6之前的JavaScript里,我们经常会通过回调函数来处理异步请求。当多个异步请求有依赖关系时,就会形成回调地狱,代码会变得很难维护。Promise可以将异步请求链式调用,避免回调地狱。
js
// 后面的请求方法,依赖前面的请求结果返回,就会写成这样
ajax(url, () => {
// 处理结果
ajax(url1, () => {
// 处理结果
ajax(url2, () => {
// 处理结果
});
});
});
Promise处理后变得清晰和直观,逻辑串行,易读
js
fetch(url).then(res => {
return fetch(url1); // 处理结果
}).then(res1 => {
return fetch(url2); // 处理结果
}) .then(res2 => {
// 处理结果
});
2.其他优势
回调函数的方式无法统一处理中间过程的请求错误,Promise通过catch可以方便地处理Promise链条上任何操作抛出的错误,而且回调函数难以组合多个异步请求的返回结果,而Promise通过then的链式调用,在最后获得汇总的结果。
二、Promise的基础用法
- Promise 基础用法,有3个状态:等待(PENDING),成功(RESOLVED), 失败(REJECTED)
js
let promise = new Promise((resolve, reject) => {
resolve('执行成功')
}).then((data) => {
console.log(data); // 成功后的处理逻辑
}, (err) => {
console.log(err); // 失败后的处理逻辑
}).finally(() => {
// 不管成功还是失败都会执行的逻辑,比如接口loading状态可以在这里修改为false
console.log(err);
})
三、实现自己的Promise
3.1 项目初始化
-
创建一个空文件夹:
mkdir promise-bysking
-
初始化一个空项目
yarn init -y
-
新建代码文件
js
touch Promise.js // Promise工具文件
touch index.js // 测试代码文件
3.2 搭建项目基础框架
index.js
js
// index.js 是一个类 new的时候支持传入一个函数,接受2个参数,拥有then方法,接受两个参数,
let Promise = require('./Promise.js').Promise;
let p = new Promise((resolve, reject) => {
resolve('执行成功')
}).then((data) => {
console.log(data);
}, (err) => {
console.log(err);
})
Promise.js
js
// 定义3个状态
const PENDING = 'PENDING'
const RESOLVED = 'RESOLVED'
const REJECTED = 'REJECTED'
class Promise {
// 接受一个执行器函数
constructor (executor) {
this.status = PENDING;// 状态初始化
this.value = undefined; // 记录成功状态的值
this.reason = undefined; // 记录失败原因
// 修改成功状态的函数
let resolve = (value) => {
this.value = value;
this.status = RESOLVED;
}
// 修改失败状态的函数
let reject = (reason) => {
this.reason = reason;
this.status = REJECTED
}
// 将修改promise状态的方法提供给执行器函数,注意这个函数是立即执行的!!!!!
executor(resolve, reject);
}
// 提供的then方法
then () {}
}
// 导出Promise工具类
module.exports = {
Promise
};
3.3 处理promise状态只能被修改一次
js
// Promise.js
const PENDING = 'PENDING'
const RESOLVED = 'RESOLVED'
const REJECTED = 'REJECTED'
class Promise {
constructor (executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
let resolve = (value) => {
if (this.status === PENDING) { // 状态是pending才能修改为成功状态
this.value = value;
this.status = RESOLVED;
}
}
let reject = (reason) => {
if (this.status === PENDING) { // 状态是pending才能修改为失败状态
this.reason = reason;
this.status = REJECTED
}
}
executor(resolve, reject);
}
then () {
}
}
module.exports = {
Promise
};
3.4 统一拦截异常报错
咱们业务代码是跑在Promise实例化时传入的executor函数里面,统一try catch后遇到错误直接调用reject方法修改promise状态为失败
3.5 实现then方法
回顾一下,咱们Promise的then方法使用,then方法接受两个函数,分别处理成功和失败两种情况
js
let p = new Promise((resolve, reject) => {
resolve()
}).then((data) => { // 传入的第一个函数,处理执行成功状态的结果
console.log(data);
}, (err) => { // 传入的第二个函数,处理执行失败状态的结果
console.log(err);
})
于是我们写出如下代码:
js
class Promise {
// ...省略其他代码
then (onfulfilled, onreject) {
if (this.status === RESOLVED) {
// 成功状态才执行传入的函数,并将promise的值作为参数传递
onfulfilled && onfulfilled(this.value)
}
if (this.status === REJECTED) {
// 失败状态才执行传入的函数,并将promise的失败原因值作为参数传递
onreject && onreject(this.reason)
}
}
}
3.6 实现then方法支持异步
原来咱们初始化Promise的时候,传入的executor是立马就执行的,试想一下,假如用户在executor里面,将修改Promise状态的逻辑放在了异步任务里面延迟执行,那么3.5小节的then方法里面由于promise的状态还是Pending状态,不满足成功或者失败的条件,两个if条件无法满足
为了解决这个问题,我们需要将同步逻辑做一些调整,我们的思路是:先收集需要执行的函数,等真正需要执行了再取出来执行(按照注释前面的序号看下面这段逻辑)
js
class Promise {
constructor (executor) {
// ...省略其他代码
this.onfulfilledArr = []; // 1.收集成功的处理回调函数
this.onrejectArr = []; // 2.收集失败的处理回调函数
let resolve = (value) => {
if (this.status === PENDING) {
this.value = value;
this.status = RESOLVED;
// 6.setTimeout时间到,用户执行了resolve,将订阅列表依次取出执行
this.onfulfilledArr.forEach(fn => {
fn()
})
}
}
let reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED
// 7.setTimeout时间到,用户执行了reject,将订阅列表依次取出执行
this.onrejectArr.forEach(fn => {
fn();
})
}
}
// ...
}
then (onfulfilled, onreject) {
if (this.status === RESOLVED) {
onfulfilled && onfulfilled(this.value)
}
if (this.status === REJECTED) {
onreject && onreject(this.reason)
}
// 3. 用户调用resolve或者reject是在异步任务里面的,就会进入这段逻辑
if (this.status === PENDING) {
// 4. 收集用户自定义的针对成功状态的处理函数,放入数组
onfulfilled && this.onfulfilledArr.push(() => {
onfulfilled(this.value)
});
// 5. 收集用户自定义的针对失败状态的处理函数,放入数组
onreject && this.onrejectArr.push(() => {
onreject(this.reason)
})
}
}
}
module.exports = {
Promise
};
3.7 处理then的链式调用
promise是支持promise().then().then().(...).then(); 链式调用的,我们看下如何处理这种场景
js
let Promise = require('./Promise.js').Promise;
let p = new Promise((resolve, reject) => {
resolve('执行成功')
}).then((data) => {
console.log(data);
}).then(data => {
console.log(data + '111');
})
then方法是在Promise的实例上的,咱们想一下,是不是在then方法执后返回一个Promsie就行?因为返回的Promise上就有then方法
首先我们需要返回一个promsie,同时原来then方法里面的逻辑也要被执行,那不妨将原来then方法里面的逻辑整体搬到新的promise的executor执行器函数里面,因为executo里面的代码也是会立即执行的,两者等效。
其次,我们返回了新的promise,那老的正在执行的promise咋办?我们的方法就是状态继承。resolve,reject是更改promise状态的函数,我们只需要新的Promise2的resolve和reject分别在旧Promise的成功回调以及失败回调的地方执行一下就能继承状态,同时注意x,y分别是上一个promise的成功回调和失败回调的返回值,获取后也会传递给新的promise,这样不仅状态继承,连成功的数据value和失败的原因reason也一起被继承,就能实现多个.the的链式调用了
如上就是源码1-1.5的内容,本期就复习一下之前学习的内容,做一个简单总结输出,后续剩余内容看大家意愿再进行更新咯
我也贴上我的源码,感兴趣自行查看 github.com/bysking/pro...
建议大家通过commit记录进行学习,事半功倍哦