作者:JowayYoung
仓库:Github、CodePen
博客:掘金、思否、知乎、简书、头条、CSDN
公众号:IQ前端
联系我:关注公众号后有我的微信哟
特别声明:原创不易,未经授权不得对此文章进行转载或抄袭,否则按侵权处理,如需转载或开通公众号白名单可联系我,希望各位尊重原创的知识产权
本文由笔者师妹LazyCurry创作,收录于笔者技术文章专栏下
我们都知道,JS是单线程的,只有前一个任务结束,才能执行下一个任务。显然在浏览器上,这样执行会堵塞浏览器对DOM的渲染。所以,JS中会有很多异步操作,那JS是如何实现异步操作呢?这就要想到Promise对象了,文本先来认识Promise,再手写代码实现Promise。
Promise是JS解决异步编程的方法之一,其英文意思是承诺。在程序中可理解为等一段时间就会执行,等一段时间就是JS中的异步。异步是指需要比较长的时间才能执行完成的任务,例如网络请求,读取文件等。Promise是一个实例对象,可从中获取异步处理的结果。
Promise有3种状态,分别是pending
(进行中)、fulfilled
(已成功)、rejected
(已失败)。只有异步操作可改变Promise的状态,其他操作都无法改变。并且状态改变后就不会再变,只能是从pending
到fulfiled
或pending
到rejected
,这也是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方法,简单地实现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开发技巧只在公众号推送