前言
我们都知道,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的功能进行解构重写。
写这篇文章的目的是为了给各位同学提供一个函数解构的思路,学会去分析一个函数的功能,从而解构出每一个步骤是如何执行和实现的,祝大家学习愉快,下次再见~