作者: JowayYoung
仓库: Github、 CodePen
博客: 掘金、 思否、 知乎、 简书、 头条、 CSDN
公众号: IQ前端
联系我:关注公众号后有我的 微信哟
特别声明:原创不易,未经授权不得对此文章进行转载或抄袭,否则按侵权处理,如需转载或开通公众号白名单可联系我,希望各位尊重原创的知识产权
本文由笔者师妹LazyCurry创作,收录于笔者技术文章专栏下
前言
我们都知道,JS是单线程的,只有前一个任务结束,才能执行下一个任务。显然在浏览器上,这样执行会堵塞浏览器对DOM的渲染。所以,JS中会有很多异步操作,那JS是如何实现异步操作呢?这就要想到Promise对象了,文本先来认识Promise,再手写代码实现Promise。
认识Promise
Promise是JS解决异步编程的方法之一,其英文意思是承诺。在程序中可理解为等一段时间就会执行,等一段时间就是JS中的异步。异步是指需要比较长的时间才能执行完成的任务,例如网络请求,读取文件等。Promise是一个实例对象,可从中获取异步处理的结果。
Promise有3种状态,分别是pending
(进行中)、fulfilled
(已成功)、rejected
(已失败)。只有异步操作可改变Promise的状态,其他操作都无法改变。并且状态改变后就不会再变,只能是从pending
到fulfiled
或pending
到rejected
,这也是Promise一个比较鲜明的特点。
使用Promise
上述已说到,Promise是一个对象,那么它肯定是由其构造函数来创建。其构造函数接受一个函数作为参数,其函数的参数有2个,分别是resolve
和reject
。resolve将状态从pending变为fulfiled
,成功时调用。reject将状态从pending变为rejected
,失败时调用。
function RunPromise(num, time) {
return new Promise((resolve, reject) => {
console.log("开始执行");
if (num % 2 === 0) {
setTimeout(() => {
resolve(`偶数时调用resolve,此时num为${num}`);
}, time);
} else {
setTimeout(() => {
reject(new Error(`奇数时调用rejected,此时num为${num}`));
}, time);
}
});
}
Promise对象上有then()
和catch()
方法。then()
接收2个参数,第一个对应resolve的回调,第二个对应reject的回调。catch()
跟then()
的第二个参数一样,用来接受reject的回调,但是还有一个作用,如果在then()
中执行resolve回调时抛出异常,这个异常可能是代码定义抛出,也可能是代码错误,而这个异常会在catch()
被捕获到。
RunPromise(22, 2000)
.then(res => {
console.log("then的第一个参数执行");
console.log(res);
console.log(newres);
}, error => {
console.log("then的第二个参数执行");
console.log(error);
})
.catch(error => {
console.log("error");
console.log(error);
});
// 输出结果如下:
// 开始执行
// then的第一个参数执行
// 偶数时调用resolve,此时num为22
// error
// ReferenceError: newres is not defined
上面例子中,RunPromise()
调用resolve
,then()
的第一个参数对应回调,状态从pending
改成fulfilled
,且状态不会再改变。在then()
中,newres这个变量尚未定义,因此程序出错,其异常在catch()
被捕获。一般来说,then()
使用第一个参数即可,因为catch()
跟then()
的第二个参数一样,还能捕获到异常。
实现Promise
Promise大致已了解清楚,也知道如何使用。为了了解Promise是如何实现的,我们手写实现一个简单的Promise方法,简单地实现then()、异步处理、链式调用。用最简单的思考方法,函数是为了实现什么功能,给对应函数赋予相应的实现代码即可。以下代码均使用ES6
进行书写。
定义Promise构造函数
创建Promise对象使用new Promise((resolve, reject) => {})
,可知道Promise构造函数的参数是一个函数,我们将其定义为implement
,函数带有2个参数:resolve
,reject
,而这2个参数又可执行,所以也是一个函数。
声明完成后,需要解决状态。上述已说过,Promise有3种状态,这里不再细说,直接上代码。
// ES6声明构造函数
class MyPromise {
constructor(implement) {
this.status = "pending"; // 初始化状态为pending
this.res = null; // 成功时的值
this.error = null; // 失败时的值
const resolve = res => {
// resolve的作用只是将状态从pending转为fulfilled,并将成功时的值存在this.res
if (this.status === "pending") {
this.status = "fulfilled";
this.res = res;
}
};
const reject = error => {
// reject的作用只是将状态从pending转为rejected,并将失败时的值存在this.error
if (this.status === "pending") {
this.status = "rejected";
this.error = error;
}
};
// 程序报错时会执行reject,所以在这里加上错误捕获,直接执行reject
try {
implement(resolve, reject);
} catch (err) {
reject(err);
}
}
}
then函数
我们在使用Promise时,都知道then()
有2个参数,分别是状态为fulfilled
和rejected
时的回调函数,我们在这里将2个函数定义为onFulfilled
和onRejected
。
class MyPromise {
constructor(implement) { ... }
then(onFulfilled, onRejected) {
// 当状态为fulfilled时,调用onFulfilled并传入成功时的值
if (this.status === "fulfilled") {
onFulfilled(this.res);
}
// 当状态为rejected时,调用onRejected并传入失败时的值
if (this.status === "rejected") {
onRejected(this.error);
}
}
}
异步处理
到这里已实现了基本的代码,但是异步时会出现问题。例如,本文一开始举例使用Promise时,resolve
在setTimeout()
中使用,这时候在then()
里,状态还是pending
,那就没办法调用到onFulfilled
。所以我们先将处理函数(onFulfilled
或onRejected
)保存起来,等到then()
被调用时再使用这些处理函数。
因为Promise可定义多个then()
,所以这些处理函数用数组进行存储。实现思路:
-
then()
增加状态为pending的判断,在此时存储处理函数 -
resolve
或reject
时循环调用处理函数
class MyPromise {
constructor(implement) {
this.status = "pending";
this.res = null;
this.error = null;
this.resolveCallbacks = []; // 成功时回调的处理函数
this.rejectCallbacks = []; // 失败时回调的处理函数
const resolve = res => {
if (this.status === "pending") {
this.status = "fulfilled";
this.res = res;
this.resolveCallbacks.forEach(fn => fn()); // 循环执行成功处理函数
}
};
const reject = error => {
if (this.status === "pending") {
this.status = "rejected";
this.error = error;
this.rejectCallbacks.forEach(fn => fn()); // 循环执行失败处理函数
}
};
try {
implement(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
if (this.status === "fulfilled") {
onFulfilled(this.res);
}
if (this.status === "rejected") {
onRejected(this.error);
}
// 当状态为pending时,说明这时还没有调用到resolve或reject
// 在这里把成功函数和失败函数存至相应的数组中,不做执行操作只做存储操作
if (this.status === "pending") {
this.resolveCallbacks.push(() => onFulfilled(this.res));
this.rejectCallbacks.push(() => onRejected(this.error));
}
}
}
测试一下异步功能,打印结果中,'执行resolve'是等待了2秒后打印出来的
new MyPromise((resolve, reject) => {
console.log("开始执行");
setTimeout(() => {
resolve("执行resolve");
}, 2000);
}).then(res => console.log(res));
// 输出结果如下:
// 开始执行
// 执行resolve
链式调用
到这里就已实现异步操作啦!吼吼~但是,我们都知道,Promise能定义多个then,就例如new Promise().then().then()
,这种就是链式调用。当然我们也要实现这个功能。
链式调用是指Promise在状态是fulfilled
后,又开始执行下一个Promise。要实现这个功能,我们只需要在then()
里返回Promise就好了,说起来好像是挺简单的。
then()
的实现思路:
-
then()
中需要返回Promise对象,我们将其命名为nextPromise
- 仍然需要判断状态,执行相应处理
-
onFulfilled
和onRejected
是异步调用,用setTimeout(0)
解决 - 需要对
onFulfilled
和onRejected
类型做判断,并做相应返回
class MyPromise {
constructor(implement) { ... }
then(onFulfilled, onRejected) {
// 如果onRejected不是函数,就直接抛出错误
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : res => res;
onRejected = typeof onRejected === "function" ? onRejected : err => { throw err; };
const nextPromise = new MyPromise((resolve, reject) => {
if (this.status === "fulfilled") {
// 解决异步问题
setTimeout(() => {
const x = onFulfilled(this.res);
RecursionPromise(nextPromise, x, resolve, reject);
}, 0);
}
if (this.status === "rejected") {
setTimeout(() => {
const x = onRejected(this.error);
RecursionPromise(nextPromise, x, resolve, reject);
}, 0);
}
if (this.status === "pending") {
this.resolveCallbacks.push(() => {
setTimeout(() => {
const x = onFulfilled(this.res);
RecursionPromise(nextPromise, x, resolve, reject);
}, 0);
});
this.rejectCallbacks.push(() => {
setTimeout(() => {
const x = onRejected(this.error);
RecursionPromise(nextPromise, x, resolve, reject);
}, 0);
});
}
});
return nextPromise;
}
}
RecursionPromise()
用来判断then()
的返回值,以决定then()
向下传递的状态走resolve
还是reject
,实现思路:
-
nextPromise
与x
不能相等,否则会一直调用自己 - 判断
x
的类型,如果不是函数或对象,直接resolve(x)
- 判断
x
是否拥有then()
,并且如果then()
是一个函数,那么就可执行x
的then()
,并且带有成功与失败的回调 -
flag
的作用是执行x
的then()
时成功与失败只能调用一次 - 执行
x
的then()
,成功时继续递归解析 - 如果
then()
不是一个函数,直接resolve(x)
function RecursionPromise(nextPromise, x, resolve, reject) {
if (nextPromise === x) return false;
let flag;
if (x !== null && (typeof x === "object" || typeof x === "function")) {
try {
let then = x.then;
if (typeof then === "function") {
then.call(x, y => {
if (flag) return false;
flag = true;
// 这里说明Promise对象resolve之后的结果仍然是Promise,那么继续递归解析
RecursionPromise(nextPromise, y, resolve, reject);
}, error => {
if (flag) return false;
flag = true;
reject(error);
});
} else {
resolve(x);
}
} catch (e) {
if (flag) return false;
flag = true;
reject(e);
}
} else {
resolve(x);
}
}
总结
具有异步处理
和链式调用
的Promise已实现啦!还有一些方法在这里就不一一实现了。毕竟实现一个完整的Promise不是一篇文章就能讲完的,有兴趣的同学可自行参照Promise的功能进行解构重写,若有写得不正确的地方请各位大佬指出。公众号后台回复promise可获取本文的源码,如果是转载的文章,可关注IQ前端再回复promise即可。
写这篇文章的目的是为了给各位同学提供一个函数解构的思路,学会去分析一个函数的功能,从而解构出每一个步骤是如何执行和实现的,祝大家学习愉快,下次再见~
结语
❤️关注+点赞+收藏+评论+转发❤️,原创不易,鼓励笔者创作更好的文章
关注公众号IQ前端
,一个专注于CSS/JS开发技巧的前端公众号,更多前端小干货等着你喔
- 关注后回复
关键词
免费领取视频教程 - 关注后添加
我微信
拉你进技术交流群 - 欢迎关注
IQ前端
,更多CSS/JS开发技巧只在公众号推送