1. 首页

一杯喜茶的时间手搓Promise

前言

我们都知道,JS是单线程的,只有前一个任务结束,才能执行下一个任务。显然在浏览器上,这样执行会堵塞浏览器对DOM的渲染。所以,JS中会有很多异步操作,那JS是如何实现异步操作呢?这就要想到Promise对象了,文本先来认识Promise,再手写代码实现Promise。

认识Promise

Promise是JS解决异步编程的方法之一,其英文意思是承诺。在程序中可理解为等一段时间就会执行,等一段时间就是JS中的异步。异步是指需要比较长的时间才能执行完成的任务,例如网络请求,读取文件等。Promise是一个实例对象,可从中获取异步处理的结果。

Promise有3种状态,分别是pending(进行中)、fulfilled(已成功)、rejected(已失败)。只有异步操作可改变Promise的状态,其他操作都无法改变。并且状态改变后就不会再变,只能是从pendingfulfiledpendingrejected,这也是Promise一个比较鲜明的特点。

使用Promise

上述已说到,Promise是一个对象,那么它肯定是由其构造函数来创建。其构造函数接受一个函数作为参数,其函数的参数有2个,分别是resolverejectresolve将状态从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()调用resolvethen()的第一个参数对应回调,状态从pending改成fulfilled,且状态不会再改变。在then()中,newres这个变量尚未定义,因此程序出错,其异常在catch()被捕获。一般来说,then()使用第一个参数即可,因为catch()then()的第二个参数一样,还能捕获到异常。

实现Promise

Promise大致已了解清楚,也知道如何使用。为了了解Promise是如何实现的,我们手写实现一个简单的Promise方法,简单地实现then()异步处理链式调用。用最简单的思考方法,函数是为了实现什么功能,给对应函数赋予相应的实现代码即可。以下代码均使用ES6进行书写。

定义Promise构造函数

创建Promise对象使用new Promise((resolve, reject) => {}),可知道Promise构造函数的参数是一个函数,我们将其定义为implement,函数带有2个参数:resolvereject,而这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); } } }

JS中文网 – 前端进阶资源教程 www.javascriptC.com
一个致力于帮助开发者用代码改变世界为使命的平台,每天都可以在这里找到技术世界的头条内容

then函数

我们在使用Promise时,都知道then()有2个参数,分别是状态为fulfilledrejected时的回调函数,我们在这里将2个函数定义为onFulfilledonRejected


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时,resolvesetTimeout()中使用,这时候在then()里,状态还是pending,那就没办法调用到onFulfilled。所以我们先将处理函数(onFulfilledonRejected)保存起来,等到then()被调用时再使用这些处理函数。

因为Promise可定义多个then(),所以这些处理函数用数组进行存储。实现思路:

  • then()增加状态为pending的判断,在此时存储处理函数
  • resolvereject时循环调用处理函数

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
  • 仍然需要判断状态,执行相应处理
  • onFulfilledonRejected是异步调用,用setTimeout(0)解决
  • 需要对onFulfilledonRejected类型做判断,并做相应返回

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,实现思路:

  • nextPromisex不能相等,否则会一直调用自己
  • 判断x的类型,如果不是函数或对象,直接resolve(x)
  • 判断x是否拥有then(),并且如果then()是一个函数,那么就可执行xthen(),并且带有成功与失败的回调
  • flag的作用是执行xthen()时成功与失败只能调用一次
  • 执行xthen(),成功时继续递归解析
  • 如果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的功能进行解构重写,若有写得不正确的地方请各位大佬指出。

写这篇文章的目的是为了给各位同学提供一个函数解构的思路,学会去分析一个函数的功能,从而解构出每一个步骤是如何执行和实现的,祝大家学习愉快,下次再见~

作者:JowayYoung
链接:https://segmentfault.com/a/1190000022294224

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:一杯喜茶的时间手搓Promise

链接:https://www.javascriptc.com/3976.html

« react Hook之useRef、useImperativeHandle
《React源码解析》系列完结!»
Flutter 中文教程资源

相关推荐

QR code