博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
前端工程师必知之Promise的实现
阅读量:6885 次
发布时间:2019-06-27

本文共 14946 字,大约阅读时间需要 49 分钟。

Promise是什么?

在Javascript的世界中,代码都是单线程执行的。这就导致我们的代码里会有嵌套回调函数,一旦嵌套过多,导致代码不容易理解和维护。

为了降低异步编程的复杂性,开发人员一直在寻找各种解决方案,Promise只是用来处理异步操作的其中一个方案。

下面我就结合着Promise的使用场景,来逐一手写Promise的实现原理。

因为ES6最终采用了Promise/A+ 规范,下面所有代码都是基于Promise/A+规范来实现。有兴趣的同学可以查看


Promise的基本使用:

let promise = new Promise((resolve,reject)=>{  resolve('success'); //这里如果是reject('fail')});promise.then((res)=>{  console.log(res); // 输出:success},(err)=>{  console.log(err); // 上面如果执行reject('fail') 这里就输出:fail});复制代码

看图就很好理解了,resolve找then里的成功回调,reject找then里失败的回调。

那么如何用我们自己的方式一步步实现这样的一个Promise类呢?直接上代码吧,我会注释得稍微详细点。

class Promise { //创建一个Promise类    constructor(executor) {        this.status = 'pending'; //初始默认状态为pending        this.value = undefined; //默认赋值为undefined        this.reason = undefined; //默认赋值为undefined        let resolve = (value) => {            if (this.status === 'pending') { //只有状态为pending才能转换状态                this.value = value; //将传递进来的的值赋给value保存                this.status = 'resolved'; //将状态设置成resolved            }        }        let reject = (reason) => {            if (this.status === 'pending') { //只有状态为pending才能转换状态                this.reason = reason; //将传递进来的失败原因赋给reason保存                this.status = 'rejected'; //将状态设置成rejected            }        }        executor(resolve, reject); //默认执行executor    }    then(onFulfilled, onRejected) { //等同于es5的Promise.prototype.then 当调用then的时候,根据状态,来执行不同的函数        if (this.status === 'resolved') { //如果状态是resolved            onFulfilled(this.value); //执行成功的resolve,并将成功后的值传递过去        }        if (this.status === 'rejected') { //如果状态是rejected            onRejected(this.reason); //执行失败的reject,并将失败原因传递过去        }    }}module.exports = Promise; //将Promise导出复制代码

现在只要在js代码中引入Promise库,就能实现最基本的Promise功能了。

输出结果:success


还有一点需要注意,就是当代码出现错误的情况下,我们需要能够捕获错误,并且处理错误。所以在executor(resolve, reject)这一行代码需要处理下,包上一层try/catch,如下:

try {    executor(resolve, reject); } catch (e) {    reject(e); //如果发生错误,将错误放入reject中}复制代码

setTimeout调用问题

此时的Promise类可以实现调用resolve,也可以调用reject。调用then之后都会执行相应的函数。看似已经没有问题了。但如果将resolve放在一个定时器里呢?看看下面的代码:

let promise = new Promise((resolve, reject) => {    setTimeout(() => {        resolve('setTimeout');    }, 500);})promise.then((res) => {    console.log(res);}, (err) => {    console.log(err);})复制代码

我们会发现上面的代码在控制台完全没有反应,这是为什么呢?实际上是因为我们压根就没有处理这种延迟调用的情况。现在我们来处理以下:

class Promise { //创建一个Promise类    constructor(executor) {        ...此处略去部分代码        this.successStore = []; //定义一个存放成功函数的数组        this.failStore = []; //定义一个存放失败函数的数组        let resolve = (value) => {            if (this.status === 'pending') { //只有状态为pending才能转换状态                ...此处略去部分代码                this.successStore.forEach(fnc => fnc()); //一次执行数组中的成功函数            }        }        let reject = (reason) => {            if (this.status === 'pending') { //只有状态为pending才能转换状态                ...此处略去部分代码                this.failStore.forEach(fnc => fnc()) //依次执行数组中的失败函数            }        }       ...此处略去部分代码    }    then(onFulfilled, onRejected) { //等同于es5的Promise.prototype.then 当调用then的时候,根据状态,来执行不同的函数        ...此处略去部分代码        if (this.status === 'pending') { //此处增加一种状态判断            this.successStore.push(() => { //当状态为pending时将成功的函数存放到数组里                onFulfilled(this.value);            })            this.failStore.push(() => { //当状态为pending时将失败的函数存放到数组中                onRejected(this.reason);            })        }    }}module.exports = Promise; //将Promise导出复制代码

这样就解决了setTimeout调用的问题了。


then的链式调用

细心的同学肯定看到了,在我们上面的代码中,其实并没有处理then的链式调用。如果此时使用then()方法的链式调用,会报错:TypeError: Cannot read property 'then' of undefined。

Promise/A+规范里说了,then()方法返回的必须是Promise实例,这里我们可以理解成返回的必须是一个新的Promise实例。所以then()方法后面可以继续跟另一个then()方法进行链式调用。

但是then()方法里可能发挥一个值或者返回的是一个Promise实例,这就需要我们分别处理这两种情况。

第一种:

let promise = new Promise((resolve, reject) => {    resolve('success');})p.then((res)=>{    console.log(res); // success    return 'hello world!' },(err)=>{    console.log(err); }).then((res)=>{    console.log(res); // hello world   },(err)=>{    console.log(err);})复制代码

在then()方法的回调里返回一个普通值,无论是成功还是失败的回调,都会进入到下一个then()的成功态里。如下:

let promise = new Promise((resolve, reject) => {    reject('fail');})p.then((res)=>{    console.log(res); },(err)=>{    console.log(err);  //fail    return 'fail';}).then((res)=>{    console.log(res);  //fail 注意:此时走的是成功回调,并非失败的回调},(err)=>{    console.log(err); })复制代码

第二种:

let promise = new Promise((resolve, reject) => {    resolve();})promise.then((res)=>{    return new Promise((resolve,reject)=>{ //返回一个新的Promise        resolve('hello world');     })},(err)=>{    console.log(err); }).then((res)=>{    console.log(res); //hello world    },(err)=>{    console.log(err);})复制代码

这两种情况都要考虑到,我们修改下代码:

function handlePromise(promise2, x, resolve, reject) {    if (promise2 === x) { //promise2是否等于x,也就是判断是否将自己本身返回        return reject(new TypeError('circular reference')); //如果是抛出错误    }    //判断x不是bull且x是对象或者函数    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {        let called; //called控制resolve或reject 只执行一次,多次调用没有任何作用。        try {            let then = x.then; //取x.then()方法            if (typeof then === 'function') { //如果是函数,就认为它是返回新的promise                then.call(x, y => {  //如果y是promise继续递归解析                    if (called) return;                    called = true;                    handlePromise(promise2, y, resolve, reject); //递归解析promise                }, r => {                    if (called) return;                    called = true;                    reject(r)                })            } else { //不是函数,就是普通对象                resolve(x); //直接将对象返回            }        } catch (e) {            if (called) return;            called = true;            reject(e);        }    } else { //x是普通值,直接走then的成功回调        resolve(x);    }}class Promise {    constructor(executor) {        this.status = 'pending';        this.value = undefined;        this.reason = undefined;        this.successStore = [];        this.failStore = [];        let resolve = (value) => {            if (this.status === 'pending') {                this.value = value;                this.status = 'resolved';                this.successStore.forEach(fn => fn());            }        }        let reject = (reason) => {            if (this.status === 'pending') {                this.reason = reason;                this.status = 'rejected';                this.failStore.forEach(fn => fn());            }        }        try {            executor(resolve, reject);        } catch (e) {            reject(e);        }    }    then(onFulfilled, onRejected) { //原型上的方法        let promise2; // 返回的新的promise        if (this.status === 'resolved') {            promise2 = new Promise((resolve, reject) => {                try {                    let x = onFulfilled(this.value);                    handlePromise(promise2, x, resolve, reject);                } catch (e) {                    reject(e);                }            })        }        if (this.status === 'rejected') {            promise2 = new Promise((resolve, reject) => {                try {                    let x = onRejected(this.reason); //x存放返回的结果                    handlePromise(promise2, x, resolve, reject); //处理返回结果的函数,已经在上面定义                } catch (e) {                    reject(e);//报错执行reject                }            })        }        if (this.status === 'pending') {            promise2 = new Promise((resolve, reject) => {                this.successStore.push(() => {                    try {                        let x = onFulfilled(this.value); //x存放返回的结果                        handlePromise(promise2, x, resolve, reject);//处理返回结果的函数,已经在上面定义                    } catch (e) {                        reject(e); //报错执行reject                    }                })                this.failStore.push(() => {                    try {                        let x = onRejected(this.reason);//x存放返回的结果                        handlePromise(promise2, x, resolve, reject);//处理返回结果的函数,已经在上面定义                    } catch (e) {                        reject(e);//报错执行reject                    }                })            })        }        return promise2; //返回新的promise    }}module.exports = Promise;复制代码

处理值穿透

先看实例

let promise = new Promise((resolve, reject)=>{    resolve('hello world');})promise.then().then().then((res)=>{     console.log(res);//我们希望可以正常打印出hello world,如何处理呢?})复制代码

只需要在then的原型方法上加上一层判断,判断then里是否传递里函数,如果没有传递,我们手动传递一个函数,并让值返回。

then(onFulfilled, onRejected) { //原型上的方法    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : y => y; //判断是否是一个函数    onRejected = typeof onRejected === 'function' ? onRejected : errr => { //判断是否是一个函数        throw err; //注意,这里不是返回值,而是抛出错误    }}复制代码

这样当我们调用Promise时就可以正常打印出hello world了。

添加catch方法

下面我们继续添加catch方法

then(onFulfilled, onRejected){    ...}catch(onRejected){ //在此处添加原型上的方法catch    return this.then(null,onRejected);}复制代码

catch()其实就是简易版的then()方法,它没有成功的回调,只有失败的回调。所以按照上面的方式处理下就可以了。

添加Promise.all方法

Promise.all = function (promiseArrs) { //在Promise类上添加一个all方法,接受一个传进来的promise数组    return new Promise((resolve, reject) => { //返回一个新的Promise        let arr = []; //定义一个空数组存放结果        let i = 0;        function handleData(index, data) { //处理数据函数            arr[index] = data;            i++;            if (i === promiseArrs.length) { //当i等于传递的数组的长度时                 resolve(arr); //执行resolve,并将结果放入            }        }        for (let i = 0; i < promiseArrs.length; i++) { //循环遍历数组            promiseArrs[i].then((data) => {                handleData(i, data); //将结果和索引传入handleData函数            }, reject)        }    })}复制代码

下面我们逐个添加reace、resolve和reject方法。

添加Promise.race方法

race比赛竞赛的意思,也就是谁跑的快就返回谁,不管你是成功还是失败。跟all方法比较相似,但更简单一些。循坏之后直接

Promise.race = function (promises) {    return new Promise((resolve, reject) => {        for (let i = 0; i < promises.length; i++) {            promises[i].then(resolve, reject);        }    })}复制代码

经过上面的代码,以下两个方法就显得很简单了,直接返回一个新的Promise,然后执行成功/失败既可。

添加Promise.resolve方法

Promise.resolve = function (val) {    return new Promise((resolve, reject) => resolve(val));}复制代码

添加Promise.reject方法

Promise.reject = function (val) {    return new Promise((resolve, reject) => reject(val));}复制代码

如果想要用promises-aplus-tests测试是否符合Promise/A+规范的话,就要加上一段代码:

Promise.deferred = Promise.defer = function () { //这是promise的语法糖  let dfd = {};  dfd.promise = new Promise((resolve,reject)=>{    dfd.resolve = resolve;    dfd.reject = reject;  })  return dfd;}复制代码

在终端安装完promises-aplus-tests后,在文件的终端下执行promises-aplus-tests "文件名"既可

npm install promises-aplus-tests -gpromises-aplus-tests filename复制代码

最后,解决下异步的问题,完整代码如下:

function handlePromise(promise2, x, resolve, reject) {    if (promise2 === x) {        return reject(new TypeError('circular reference'));    }    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {        let called;        try {            let then = x.then;            if (typeof then === 'function') {                then.call(x, y => {                    if (called) return;                    called = true;                    handlePromise(promise2, y, resolve, reject);                }, r => {                    if (called) return;                    called = true;                    reject(r)                })            } else {                resolve(x);            }        } catch (e) {            if (called) return;            called = true;            reject(e);        }    } else {        resolve(x);    }}class Promise {    constructor(executor) {        this.status = 'pending';        this.value = undefined;        this.reason = undefined;        this.successStore = [];        this.failStore = [];        let resolve = (value) => {            if (this.status === 'pending') {                this.value = value;                this.status = 'resolved';                this.successStore.forEach(fn => fn());            }        }        let reject = (reason) => {            if (this.status === 'pending') {                this.reason = reason;                this.status = 'rejected';                this.failStore.forEach(fn => fn());            }        }        try {            executor(resolve, reject);        } catch (e) {            reject(e);        }    }    then(onFulfilled, onRejected) {        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : y => y;        onRejected = typeof onRejected === 'function' ? onRejected : errr => {            throw err;        }        let promise2;         if (this.status === 'resolved') {            promise2 = new Promise((resolve, reject) => {                setTimeout(() => { //异步处理                    try {                        let x = onFulfilled(this.value);                        handlePromise(promise2, x, resolve, reject);                    } catch (e) {                        reject(e);                    }                }, 0);            })        }        if (this.status === 'rejected') {            promise2 = new Promise((resolve, reject) => {                setTimeout(() => { //异步处理                    try {                        let x = onRejected(this.reason);                        handlePromise(promise2, x, resolve, reject);                    } catch (e) {                        reject(e);                    }                }, 0);            })        }        if (this.status === 'pending') {            promise2 = new Promise((resolve, reject) => {                this.successStore.push(() => {                    setTimeout(() => { //异步处理                        try {                            let x = onFulfilled(this.value);                            handlePromise(promise2, x, resolve, reject);                        } catch (e) {                            reject(e);                        }                    }, 0);                })                this.failStore.push(() => {                    setTimeout(() => { //异步处理                        try {                            let x = onRejected(this.reason);                            handlePromise(promise2, x, resolve, reject);                        } catch (e) {                            reject(e);                        }                    }, 0);                })            })        }        return promise2;    }}Promise.all = function (promiseArrs) {    return new Promise((resolve, reject) => {        let arr = [];        let i = 0;        function processData(index, data) {            arr[index] = data;            i++;            if (i === promiseArrs.length) {                resolve(arr);            }        }        for (let i = 0; i < promiseArrs.length; i++) {            promiseArrs[i].then((data) => {                processData(i, data);            }, reject)        }    })}Promise.deferred = Promise.defer = function () {    let dfd = {};    dfd.promise = new Promise((resolve, reject) => {        dfd.resolve = resolve;        dfd.reject = reject;    })    return dfd;}module.exports = Promise;复制代码

现在,一个符合Promise/A+规范的Promise类已经完成了。见解有限,如有描述不准确之处,请帮忙及时指出。如有错误,一定会及时更正。

参考

转载地址:http://mhnbl.baihongyu.com/

你可能感兴趣的文章
AHSC DAY2总结
查看>>
java.lang.SecurityException: class "javax.servlet.FilterRegistration"(spark下maven)
查看>>
[Vue CLI 3] 配置解析之 css.extract
查看>>
Linux——信息采集(三)dmitry、路由跟踪命令tracerouter
查看>>
提取ipa里面的资源图片 png
查看>>
wxpython ItemContainer
查看>>
工作中 Oracle 常用数据字典集锦
查看>>
SFB 项目经验-12-为某上市企业的Skype for Business购买Godday证书
查看>>
[C#基础知识]专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
查看>>
大数据虚拟化零起点-2基础运维第一步-环境规划和准备
查看>>
Skype for Business Server 2015-04-前端服务器-3-安装-管理工具
查看>>
docker入门指南(转载)
查看>>
Java RGB数组图像合成 ImageCombining (整理)
查看>>
第八届河南省赛F.Distribution(水题)
查看>>
Android 下拉刷新上拉载入效果功能
查看>>
第九篇 :微信公众平台开发实战Java版之如何实现自定义分享内容
查看>>
SDL2源码分析1:初始化(SDL_Init())
查看>>
swift通过摄像头读取每一帧的图片,并且做识别做人脸识别
查看>>
你对自己的定位是什么,就能成为什么样的人(转)
查看>>
全文检索引擎Solr系列——整合中文分词组件IKAnalyzer
查看>>