JavaScript 关于Promise 对象的知识梳理

(1)认识

Promise 对象是 JavaScript 的异步操作解决方案,为异步操作提供统一接口。它起到代理作用(proxy),充当异步操作与回调函数之间的中介,使得异步操作具备同步操作的接口。
Promise 的设计思想是,所有异步任务都返回一个 Promise 实例。Promise 实例有一个then方法,用来指定下一步的回调函数。
可以简单的将Promise理解为一个容器,里面保存着未来才会结束的事件的结果。

(2)Promise 对象的状态

Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态。

【1】异步操作未完成(pending)
【2】异步操作成功(fulfilled)
【3】异步操作失败(rejected)
上面三种状态里面,fulfilled和rejected合在一起称为resolved(已定型)。

这三种的状态的变化途径只有两种。
【1】从“未完成”到“成功”
【2】从“未完成”到“失败”
一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是 Promise 这个名字的由来,它的英语意思是“承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise 实例的状态变化只可能发生一次。
因此,Promise 的最终结果只有两种。异步操作成功,Promise 实例传回一个值(value),状态变为fulfilled。异步操作失败,Promise 实例抛出一个错误(error),状态变为rejected。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

(3)Promise 构造函数

JavaScript 提供原生的Promise构造函数,用来生成 Promise 实例。

    let promise = new Promise(function (resolve, reject) {
        console.log("处理Promise任务...")
        // 判断当前任务的处理结果
        if (true) {
            // 执行成功
            resolve("ok");
        } else {
            // 执行失败
            reject(new Error());
        }
    });

    console.log(promise);

输出结果:
在这里插入图片描述
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己实现。

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。这两个函数都是可选的,不一定要提供。它们都接受Promise对象传出的值作为参数。

需要注意当前Promise执行任务的结果,并不是说里面的代码执行完毕就结束了,如果有一个then()的话,但是在promise并没有去声明处理结果即resolve或者reject,那么是无法继续执行then里面的流程的。

    let p1 = new Promise(function (resolve, reject) {
        console.log("f1()...")
    })

    function f2() {
        console.log("f2()...")
    }
    
    p1.then(f2)

上面的代码执行结果如下:
在这里插入图片描述
并没有去执行then中的f2方法代码,这是因为Promise的代码还没有一个确切的结果。

    let p1 = new Promise(function (resolve, reject) {
        console.log("f1()...")
        resolve("ok")
    })

    function f2(param) {
        console.log("f2()..." + param)
    }
    p1.then(f2)

在这里插入图片描述
增加代码行resolve(“ok”)执行完毕,其中还将执行结果作为参数传递出去,并且在then方法中获取到了该参数。

(4)Promise.prototype.then()

Promise 实例的then方法,用来添加回调函数。

then方法可以接受两个回调函数,第一个是异步操作成功时(变为fulfilled状态)的回调函数,第二个是异步操作失败(变为rejected)时的回调函数(该参数可以省略)。一旦状态改变,就调用相应的回调函数。

    let p1 = new Promise(function (resolve, reject) {
        console.log("f1()...")
        // 这里分别启用进行测试
        reject(new Error('失败'));
        // resolve("完成")
    })

    function f2(param) {
        console.log("f2()..." + param)
    }

    function f3(param) {
        console.log("f3()..." + param)
    }

    p1.then(f2, f3)

在这里插入图片描述
上面的代码中分别对成功和失败进行了测试,当resolve时执行第一个参数函数,即f2,当reject时执行第二个参数,即f3。

then方法可以链式使用。

p1
  .then(step1)
  .then(step2)
  .then(step3)
  .then(
    console.log,
    console.error
  );

【1】可以通过链式的方式执行多个流程
【2】可以不指定reject处理函数
【3】上面代码中如果上一步的处理结果是resolve,那么会依次执行所有的,直到console.log()
【4】如果上面任意一个step流程发生异常,那么Promise就会去寻找reject处理函数,上面代码中只有一个就是console.error()
【5】在then中的方法可以不指定resolve或者reject,后面也可以继续执行
【6】想要获取上一步的处理结果可以通过return的方法返回结果,并在下一步中通过参数获取

    function f2(param) {
        console.log("f2()..." + param)
        return param
    }

    function f3(param) {
        console.log("f3()..." + param)
        return param
    }

    function f4(param) {
        console.log("f4()..." + param)
        return param
    }

    function f5(param) {
        console.error("异常:", param)
    }

    p1.then(f2).then(f3).then(f4).then(console.log, f5)

在这里插入图片描述
【7】基于上面的代码如果某一步发生了异常,那么就会寻找下一个reject函数,即f5。此外,当某一步发生了异常,那么后面的step不会在执行了。


    function f3(param) {
        console.log("f3()..." + param)
        throw new Error("ERROR")
    }

在这里插入图片描述

(5)微任务

Promise 的回调函数属于异步任务,会在同步任务之后执行。

    new Promise(function (resolve, reject) {
        resolve(1);
    }).then(console.log);

    console.log(2);
    // 输出:2 1

上面的输出结果是先输出2后输出Promise里面的代码,异步任务要晚于同步任务。但是,Promise 的回调函数不是正常的异步任务,而是微任务(microtask)。它们的区别在于,正常任务追加到下一轮事件循环,微任务追加到本轮事件循环。这意味着,微任务的执行时间一定早于正常任务。

setTimeout(function() {
  console.log(1);
}, 0);

new Promise(function (resolve, reject) {
  resolve(2);
}).then(console.log);

console.log(3);
// 3
// 2
// 1

上面代码的输出结果是321。这说明then的回调函数的执行时间,早于setTimeout(fn, 0)。因为then是本轮事件循环执行,setTimeout(fn, 0)在下一轮事件循环开始时执行。

(6)图片加载

    var loadImage = function (path) {
        return new Promise((resolve, reject) => {
            let image = new Image();
            image.onload = resolve
            image.onerror = reject
            image.src = path
        })
    }
    // 加载图片
    loadImage("https://www.runoob.com/try/demo_source/logo.png").then(function (e) {
        e.target.width = 400
        e.target.height = 400
        document.getElementById("myImg").appendChild(e.target);
    }).then(function () {
        console.log("图片加载成功")
    }, function () {
        console.log("图片加载失败")
    })

在这里插入图片描述
【1】image是一个图片对象的实例。它有两个事件监听属性,onload属性在图片加载成功后调用,onerror属性在加载失败调用。
【2】上面的image.onload = resolve和 image.onerror = reject就是把图片加载完成和异常和Promise进行了绑定
【3】图片加载成功以后,onload属性会返回一个事件对象,因此第一个then()方法的回调函数,会接收到这个事件对象。该对象的target属性就是图片加载后生成的 DOM 节点。
【4】扩展小知识:Image()函数将会创建一个新的HTMLImageElement实例。

var myImage = new Image(100, 200);
myImage.src = 'picture.jpg';
document.body.appendChild(myImage);

上面的代码相当于在 中定义了下面的 HTML:

<img width="100" height="200" src="picture.jpg">

实现AJAX

    const myAjax = function (url) {
        return new Promise((resolve, reject) => {
            // 定义handler
            const handler = function () {
                if (this.readyState !== XMLHttpRequest.DONE) return;
                if (this.status === 200) {
                    // 请求成功,将响应结果返回
                    resolve(this.response)
                } else {
                    // 请求失败,返回异常信息
                    reject(new Error(this.statusText))
                }
            }
            // 创建XMLHttpRequest对象
            let client = new XMLHttpRequest();
            client.onreadystatechange = handler
            client.open("GET", url)
            client.responseType = "json"
            client.setRequestHeader("Accept", "application/json")
            client.send()
        })
    }
    myAjax("a.json").then(function (json) {
        console.log('响应结果: ' + JSON.stringify(json));
    }, function (error) {
        console.error('发生异常', error);
    });

在这里插入图片描述
上面代码中,是对 XMLHttpRequest 对象的封装,用于发出一个针对 JSON 数据的 HTTP 请求,并且返回一个Promise对象。需要注意的是,在其内部,resolve函数和reject函数调用时,都带有参数。

如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例

小结

【1】Promise 的优点在于,让回调函数变成了规范的链式写法,程序流程可以看得很清楚。它有一整套接口,可以实现许多强大的功能,比如同时执行多个异步操作,等到它们的状态都改变以后,再执行一个回调函数;再比如,为多个回调函数中抛出的错误,统一指定处理方法等等。

【2】Promise 还有一个传统写法没有的好处:它的状态一旦改变,无论何时查询,都能得到这个状态。这意味着,无论何时为 Promise 实例添加回调函数,该函数都能正确执行。所以,你不用担心是否错过了某个事件或信号。如果是传统写法,通过监听事件来执行回调函数,一旦错过了事件,再添加回调函数是不会执行的。

【3】Promise 的缺点是,编写的难度比传统写法高,而且阅读代码也不是一眼可以看懂。你只会看到一堆then,必须自己在then的回调函数里面理清逻辑。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZWZhangYu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值