美文网首页
我眼中的Promise对象是怎么使用的?

我眼中的Promise对象是怎么使用的?

作者: baixin | 来源:发表于2020-08-24 15:41 被阅读0次
Promise状态过程图

目录

1、更友好地处理异步函数的异常问题
2、更友好地处理异步函数的返回值
3、解决地狱回调函数(Callback Hell)
4、看起来更像同步代码编程风格
5、隔离变化点
6、then方法返回一个新实例
7、实现链式调用风格


Promise简介

Promise是有“承诺”之意;

承诺解释:人与人之间,一个人对另一个人所说的具有一定憧憬的话,一般是可以实现的。对某项事务答应照办。(来自百度词条)

Promise A+是什么?

An open standard for sound, interoperable JavaScript promises—by implementers, for implementers.
译:它一个开放标准;它是由互操作JavaScript语言Promises实现的;

Promise A+只提供了一个then方法,但允许实现者实现其它的方法更多同时要遵循一定的规范;

其它方法如:catch()、finally()、all()、race()、allSettled()、any()、resolve()、reject()、try()等等;

更多详情查看Promise A+标准;

Promise

Promise 是异步编程的一种解决方案,比传统的异常编程解决方案——回调函数和事件——更安全、更合理、更强大;接下来我们将通过Promise特点来分析Promise的更安全、更合理、更强大特性;

Promise的特点

1、更友好地处理异步函数的异常问题

为了更好的说明异步函数异常问题,我们先看一下同步函数是如何报出异常的;

接下来看一段同步函数伪代码:

同步函数伪代码

1、调用foo函数进入foo函数体;
2、在foo函数体内调用callback(bar)函数
3、a/b会抛出b未定义的异常, 在bar函数体中没有捕获它的catch,向调用这的地方抛出;
4、来到了foo函数体的callback,在foo函数体中没有捕获它的catch,继续向调用foo的地方报出;
5、在调用foo函数处发现有有try/catch,就会调用catch语句把错误传给它;

同步函数是当一个函数的函数体内的代码执行完毕后,返回了结果者会继续向下执行;
所以异常情况会得到了捕获;

接下来看一段同步函数伪代码:

在ES6之前是没有办法声明异步函数的,javascript提供的都是内置的异步函数,如:setTimeout是一个异步函数,我们测试一下setTimeout是否能够提出异常?

首先把上面代码拷贝下来,然后把foo函数改成setTimeout函数并且删除无用foo函数

异步函数伪代码

没有捕获成功,抛出异常了,异步函数不支持“冒泡”抛出异常;所以在调用处捕获不到它;

异步函数无法捕获异常情况,在程序中是不允许的,我们要想办法解决它?

Promise可以帮助我们更好的解决这个问题;

接下来看一段同步函数伪代码:


function bar(a) {
    a / b;
}

// 这是一个用Promise模拟的异步函数;
function asyncFn(callback, number) {
    return new Promise((resolve, reject) => {
        callback();
    });
}
let promise = asyncFn(bar, 10);

promise.catch((data) => {
    console.log("抛出的异常我捕获到了!");
});

asyncFn相当于setTimeout方法;不一样的是这个方法会返回一个Promise对象,调用对象的catch方法就可以捕获到异常;
用支持Promise的asyncFn替换setTimeout函数;说明Promise可以捕获到抛出的异常;

这个async是我们模拟的异步函数;这个函数看起来有点复杂,如果是一个javascript提供的原生异步函数不会向上面这样还需要实例化一个Promise对象,异步函数看起来会更像这样的伪代码:

function bar(a) {
    a / b;
}

// 这是一个用Promise模拟的异步函数;
async function asyncFn(callback, number) {
    callback();
}

let promise = asyncFn(bar, 10);

promise.catch((data) => {
    console.log("抛出的异常我捕获到了!");
});

注意:以上代码是伪代码,为了演示捕获异步函数抛出的异常的特点只考虑了一种情况就是抛出异常;

2、更友好地处理异步函数的返回值

我们看一个ajax请求的问题

function ajax(url, method, callback) {
    var xhr = new XMLHttpRequest();
    xhr.responseType = "json";
    xhr.open(method, url, true);
    xhr.send();
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
            callback(xhr.response);
        }
    }
}
ajax("https://www.jianshu.com/p/7c53c95d664f", "get", function (response) 
        var content =response;
        /**************************************************/
        /*********这里省略掉的是对content的业务处理代码*********/
        /**************************************************/
    
});

通过这种方式我们可以正确地得到了content,同时也可以做下面的业务处理;

这种方式能满足我们的要求,但不是很合理?
首先我们分析这种方式为什么不合理?
第一、使用回调函数会增加人我们对ajax理解复杂度,原因是,从ajax函数的角度来看,当我调用了ajax函数并输入了正确的参数,执行完函数体后,就应该得到函数的返回值(同步函数就是很直接这么干的,哈哈!)才对,并不希望再加一个回调函数;
第二、从重构代码的角度来看,一个函数最好不要有参数,最坏不要超过三个参数,如果超过了就应该想办法重构它,参数越多函数的复杂度就越大;

接下来看一段Promise伪代码:

function ajax(url, method) {
    return new Promise((resolve) => {
        var content;
        var xhr = new XMLHttpRequest();
        xhr.responseType = "json";
        xhr.open(method, url, true);
        xhr.send();
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 && xhr.status === 200) {
                resolve(xhr.response);
            }
        }
        return content;
    });

}
var promise = ajax("https://www.jianshu.com/p/7c53c95d664f", "get");

promise.then(function (response) {
    var content = response;
    /**************************************************/
    /*********这里省略掉的是对content的业务处理代码*********/
    /**************************************************/

});

这种方式比使用回调函数的解决方案更合理,我们来对比一下;

解决方案 参数 返回值
回调函数 参数多 在回调函数中获取
promise 参数少 在ajax的返回对象(promise)中更获取,更像是直接返回了值

注意:这里没有考虑ajax的异常情况

3、解决地狱回调函数(Callback Hell)

地狱回调是更友好地处理异步编程的重头戏,也是大家常常提起的一大亮点;在这里我不过多的分析它,原因是大家都已经很熟悉它了;在面试中也会常常问题;

好我们现在开始说一说地狱回调;

在进行HTTP的API编程时会常常用到它;

假设,我们有三个HTTP API的请求,这三个请求的逻辑是:第一个API的结果是第二个API的请求参数,第二个API的结果是第三个API的请求参数;怎么解决它呢?

有三个解决办法:
第一、把第三个API请求体放到第二个API请求的onreadystatechange函数中,把第二个API请求体放到第一个API请求的onreadystatechange 函数中;这是极力不推荐的;这也是最严重的地狱回调方式,相信很少有人这么干。这里就不给出伪代码了,相信你已经想像出它的代码结构了;
第二、就是把ajax请求封装起来,就像前面那些使用ajax函数对XMLHttpRequest封装,把需要的参数输入参数中,这样提高代码可重用性;这个也不给出代码了,相信大家以前也这么干过。
第三、使用 Promise应该是大家现在常用的方式,包括在Node.js的异常编程用的最多了。
如果我们使用Promises处理相同的操作,因为我们可以附加回调而不是传递回调;
接下来看一段伪代码:

function ajax(request) {
    return new Promise((resolve) => {
        var xhr = new XMLHttpRequest();
        xhr.responseType = "json";
        xhr.open(request.method, request.url, true);
        xhr.send(request.data);
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 && xhr.status === 200) {
                resolve(xhr.response);
            }
        }
    });

}
// 这只是实现嵌套API调用的其中一种方式而已;
Promise.resolve()
.then(() => {
    let request = {
        url: "第一个API地址",
        method: "post",
        data: null
    };
    return ajax(request)
})
.then((response) => {
    let request = {
        url: "第二个API地址",
        method: "post",
        data: response.data // 在实际的开发中不会这么干,还要再做进一步处理,这里只是演示;
    }
    return ajax(request)
}).then((response) => {
    let request = {
        url: "第三个API地址",
        method: "post",
        data: response.data // 在实际的开发中不会这么干,还要再做进一步处理,这里只是演示;
    }
    return ajax(request)
}).then((data)=>{
    console.log(data);
});

这种方式可读性更强;在这里不过多的解释了,如果有不明白的地方,就请留言吧!

前也许有的前端同学会问,这个方式一看就能明白,自己平时学习时也是这么理解的,为什么在工作中想不起来用它,或者就用不到这么多的then处理;这是很正常的。
原因1:和项目有关,如果你的项目小型的并且是单体系统的话,请求一个API就能满足了你想要的数据,不需要像上面这么嵌套多个API;
原因2:如果是一个大系统的话,后端开发同学已经帮前端同学做了一定的处理,前端同学只需要调用一个API请求,原因在于,如果让前端同学处理的话,会产生高延迟,降低响应速度,对用户来说体验也不佳;

对于上面的二点原因,任何事情都不是绝对的而是相对的情况,无认是大系统还是小系统都和架构有关,还要根据项目综合考虑;

单体系统:指所有的业务逻辑都放在一个服务器上,像一个普通的网站就一个最简单的单系统

4、看起来更像同步代码编程风格

这个特点与解决地狱回调函数特点很相似,但是出发点不同;

  • 解决地狱回调函数也符合实现同步函数编程风格的特点;
  • 实现同步函数编程风格不仅仅是为了解决地狱回调函数特点而存在,还有其它的原因;

如果一个异步函数只调用了一个then或catch方法也符合这个特点。这个特点要和传统的回调函数想相比,虽然都是异步编程,但是他们的编程风格不一样。

这里就不给出代码了。

( 模块化有多种方式,如,函数、对象、文件;这里模块特指是函数)

5、隔离变化点

简单来说,隔离变化点的意思就是要分开处理,把稳定的部分和变化的部分分离开来,只维护变化的部分;

看一下下面的伪代码:

function ajax(request) {
    return new Promise((resolve) => {
        var xhr = new XMLHttpRequest();
        xhr.responseType = "json";
        xhr.open(request.method, request.url, true);
        xhr.send(request.data);
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4 && xhr.status === 200) {
                resolve(xhr.response);
            }
        }
    });

}


let promise = ajax({
    url: "请求地址",
    method: "post",
    data: null
}).then(() => {
    console.log(data.name);

    for(var i = 0; i < data.project.length; i++) {
        console.log(data.project[i].name)
    }

    for(var i = 0; i < data.project.length; i++) {
        console.log(data.work[i].name)
    }
});

这段代码实现了隔离变化点;

从代码中看到,ajax函数的代码是相对稳定的,无论调用多少个api都不需要改这里的东西。
请求参数和调用promise的then方法是变化的,如果以后维护它或处理响应的逻辑都不需要修改ajax函数部分,从代码的角度来看,提高了复用性;

隔离变化点也降低了耦合性,提高了内聚性;

再多说一句,如果ajax的函数中的XMLHttpRequest想换成fetch对象或axios对象,就会变得很方便了,不需要修改下面的业务逻辑代码。

6、then方法返回一个新实例

Promise实现了Promise A+开发标准,标准只提供了一个then抽象方法;调用then方法后一般情况都会返回一个新实例,由于这个特点的存在,同样也实现同步函数编程风格、链式调用风格。

其实代码和前面的很相似,这里不给代码了。

这个具体的使用可以参考 Promise A+,这里有更详细的说明;

7、实现链式调用风格

当你看到这个特点时,已经不需要我再多说什么了!以上几个特点已经很明白地说明了实现链式调用风格。同步函数也有链式调用风格;

我们仍然在Promises中使用回调函数,但是以不同的方式(链接)使用。

这里再多说一下,链式调用是不是有点像组合函数; 一个函数的输出是别一个函数的输入

总结

  • 以上几个特点它们有关联性,实现了一个特点,其它的几个特点也相应地实现,如:解决地狱回调,隔离变化点、同步函数编程风格、链式调用风格等;
  • 以上几个特点可以再归类,这里没有归类的原因是想表达出更详细的特点,方便大家理解;
  • 我们理解Promise对象,对我们以后使用async异步函数是有很大帮助的;

缺点

  • Promise在链式调用的过程中不能中途中止;

以上就是我在使用Promise时需要考虑的几点;自己也在学习中。。。有不足的地方欢迎大家留言指正,谢谢!

(完)

相关文章

  • 我眼中的Promise对象是怎么使用的?

    目录 1、更友好地处理异步函数的异常问题2、更友好地处理异步函数的返回值3、解决地狱回调函数(Callback H...

  • Promise原理解析

    1、Promise 简介及使用 1.1、Promise 简介 Promise 对象是 CommonJS 工作组提出...

  • ES6必知必会 (五)—— Promise 对象

    Promise 对象 1.Promise对象是ES6对异步编程的一种解决方案,它有以下两个特点: Promise对...

  • async/await

    async实际是对promise上的扩展,使用了promise并不和promise冲突 - then 将callb...

  • Promise小结

    Promise 1、怎么使用Promise 任务成功调用 resolve(result)任务失败调用 reject...

  • 使用ES5实现ES6中的Promise API

    使用ES5手动实现ES6中的Promise API Promise 对象是一个代理对象(代理一个值),被代理的值在...

  • Promise、Generator 函数

    Promise对象是一个构造函数,用来生成Promise实例 resolve函数改变promise状态从Pendi...

  • 理解promise

    ES6---new Promise()讲解,Promise对象是用来干嘛的?原文链接:https://blog.c...

  • 37.promise讲解

    在promise出现之前,对异步请求的处理方式如下 执行结果: 什么是promise和基本使用 Promise是一...

  • Koa基础 Promise函数

    1. Promise 函数的特性 ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。 P...

网友评论

      本文标题:我眼中的Promise对象是怎么使用的?

      本文链接:https://www.haomeiwen.com/subject/hbgvjktx.html