14_1.ES6中的 Promise, 学习一下

start

  • Promise中文释义: 承诺
  • Promise这个单词截止到现在,番茄都不知道敲过多少次了,工作中经常会使用到,但是我觉得我对它掌握程度不够熟练。
  • 今天再来理解理解Promise,然后手撕一个出来。YES,祝我好运!
  • 学习Promise之前,建议JS基础要打好,基本的原型this指向new 都需要掌握。

1.Promise是什么?

1.1 概念

Promise是异步编程的一种解决方案,比传统的解决方案:“回调函数和事件” 更合理和更强大。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

1.2 粗略的理解一下Promise

抽象理解

  • Promise 是一门新的技术(ES6 规范)

  • Promise 是 JS 中进行异步编程的新解决方案

    备注:旧方案是单纯使用回调函数

具体表达

  • 从语法上来说: Promise 是一个构造函数
  • 从功能上来说: Promise 对象用来封装一个异步操作并可以获取其成功/ 失败的结果值

2.为什么要是用Promise?

2.1 异步编程的常见场景

  1. 定时器
  2. ajax请求
  3. nodejs的文件操作模块(fs模块) ==>读取文件时是异步的。

1.定时器

// 浏览器环境
setTimeout(function () {
    console.log('1.定时器')
}, 2000)

2.ajax请求

// 浏览器环境
const xhr = new XMLHttpRequest()

xhr.open('get', 'http://www.baidu.com')

xhr.send()

xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300) {
            console.log('2.ajax请求',xhr.status)
        } else {
            console.log('请求失败: status: ' + xhr.status)
        }
    }
}

3.nodejs中读取文件

// nodejs环境
const fs = require('fs');

fs.readFile('1.promise.md', (err, data) => {
    // 如果出错 则抛出错误
    if (err) throw err;
    //输出文件内容
    console.log('3.nodejs中读取文件',data.toString());
});

throwtrycatch 不了解的可以看下JS错误处理的博客

2.2 旧的处理方案(单纯使用回调函数)

  1. 就以读取文件为例,如果我们只读取一个文件,ok没问题,很简单。
const fs = require('fs');

fs.readFile('1.promise.md', (err, data) => {
    // 如果出错 则抛出错误
    if (err) throw err;
    //输出文件内容
    console.log(data.toString());
});
  1. 假如我希望,在读取完第一个文件之后读取第二个文件,依次类推,直到读取到最后一个文件。
const fs = require('fs');

// 1.读取第一个文件
fs.readFile('1.promise1.md', (err, data) => {
    if (err) throw err;
    console.log(data.toString());
   

    // 2.读取第二个文件
    fs.readFile('1.promise2.md', (err, data) => {
        if (err) throw err;
        console.log(data.toString());
      
      
        // 3.读取第三个文件
        fs.readFile('1.promise3.md', (err, data) => {
            if (err) throw err;
            console.log(data.toString());
        });
    });
});

由于需求必须等前面一个文件读取完,再读取后面一个文件。所以目前只能把读取后面文件的代码,写在前一个读取文件的回调函数中。写到这里,问题就很明显了:

  1. 我现在是读取三个文件,假如这个文件数量很多怎么办?嵌套的括号层级肯定会非常多。远看一下代码的代码结构就类似 > 这种结构了,这就叫做回调地狱。这样就导致阅读起来会非常难受!
  2. 假如读取过程中某一个地方出错了,我们如何快速处理异常?
  3. 回调函数必须提前写好,不是很方便。

2.3 为什么使用Promise?

1-指定回调函数的方式更加灵活

  • 旧的: 必须在启动异步任务前指定
  • Promise: 启动异步任务 => 返回Promie对象 => 给Promise对象绑定回调函 数(甚至可以在异步任务结束后指定/多个)

2-支持链式调用, 可以解决回调地狱问题

  • 旧的:回调地狱,不便于阅读,不便于异常处理
  • Promise链式调用,来解决回调地狱问题,回到方式

3.async awiat

  • Promise只是简单的改变格式,并没有彻底解决上面的问题真正要解决上述问题,promise +async/await实现异步传同步。

这个在以后的 awaitasync的博客再细说。

3. Promise怎么用?

ps:我第一次看到它的用法感觉很难,现在再看它,其实很简单,加油共勉。

1.犹豫不决就控制台打印一下!

// 打印出来 Promise 它是一个方法,我们换个方式继续打印
console.log(Promise) 

// `console.dir` 可以打印一个对象所有的属性和方法。  
//(ps:由上面的console.log,我们知道它是一个函数。函数本身也是一个特殊的对象,我们打印一下看看它有哪些属性和方法)
console.dir(Promise) 

图1

①:可以得知Promise本身是一个函数;
②:这个函数对象本身有一些自己的方法,例如:(all,race,resolve,reject
③:它的portotype(显式原型)上除了默认的constructor,还有一些独有的方法,例如:(thencatchfinally)

2. 去MDN官网瞅瞅

进去一脸懵逼,出来一脸懵逼 (/ω\)…

3.看看有没有别的教程

阮一峰的ES6_Promise教程

4.开始使用

看了上面的博客我们可以知道Promise的基本用法:

const p = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

console.log(p)

解读一下上面的代码

  1. 我们用console打印的时候,已知Promise是一个函数。这里的基本用法是new了一下它,ok ,它是一个构造函数。
  2. 构造函数就构造函数,见得多了,比如我们创建一个对象 可以:var obj = new Object()。但是为什么我第一次见到它,就潜意识觉得它很难呢,其实就是它这个构造函数,传入了一个匿名函数作为了实参!!
    我把代码改吧改吧,让我们理解起来简单点
const p = new Promise(
     

    // 1.我 new Promise的时候,传入了一个匿名函数进去,仅此而已
    function (resolve, reject) {
        // 2.这里面可以做一些逻辑处理 同步异步皆可
        // ... some code

        // if (/* 异步操作成功 */) {
        //     resolve(value);
        // } else {
        //     reject(error);
        // }
    }

);

console.log(p)
  1. 它这个匿名函数呢,也有两个形参,一个resolve, 一个reject,
  • 直接在这两个形参后面加了括号,加了括号代表什么? 加了括号表示 执行
  • 什么代码能执行,函数啊
  • 这两个函数还传入了实参 valueerror
  1. 那我们把代码改成这样的,去运行一下尝试一下。
// 1. 依据 构造函数:Promise,new了一个实例p
const p = new Promise(
    // 2.在new Promise的时候,传入了一个匿名函数作为参数
    function (resolve, reject) {
        // 3.这里为了了解这个匿名函数的参数有什么用,我这里执行一下它
        resolve('lazy');

        reject('tomato');
    }
);

console.log('p是什么:',p)
  1. 运行截图

在这里插入图片描述

  1. 截图中的这个[[PromiseState]]是什么?
  • 括起来的双方括号[[PromiseState]]表示它是一个内部属性,不能在代码中直接访问。
  • 我之前纠结过这个地方,很好奇为什么会有这种形式的属性,查询了官方ECMA的文档。我个人简单理解,就是这个对象有一个内部属性,代码无法访问也无法定义这种属性。
  • 其次按照英译理解一下这个属性,[[PromiseState]]就是这个实例的状态

5. p(Promise实例)的状态

  1. 实例对象p中的一个属性 [[PromiseState]],它的值有三种情况,

    1. pending 未决定的

    2. resolved / fullfilled 成功

    3. rejected 失败

    State => 状态PromiseState=> Promise的状态

  2. 实例对象p(Promise实例)的 状态改变 有哪几种情况

    1. 由默认的pending 变为 resolved
    2. 由默认的pending 变为 rejected

    说明: 只有这 2 种, 且一个 promise 对象只能改变一次 无论变为成功还是失败, 都会有一个结果数据 成功的结果数据一般称为 value, 失败的结果数据一般称为 reason

  3. 如何改变 实例对象p(Promise实例) 的状态
    new Promise()传入的匿名函数中,以下情况,会改变实例对象的状态。

    1. resolve(value): 如果当前状态是 pending 就会变为 resolved
    2. reject(reason): 如果当前状态是 pending 就会变为 rejected
    3. 抛出异常: 如果当前状态是 pending 就会变为 rejected

6. 实例的 then() 方法

实例的then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。

7.开始使用Promise

同步的 Promise

// 说下执行顺序


const p = new Promise(  // 1
    function (resolve, reject) { // 2
        resolve('lazy'); // 3
        console.log(1) // 4
        reject('tomato'); // 实例的状态已经改变了,这里不会再改变状态了
    }
);

// 5 这里注意一下,状态改变 先与 绑定回调函数 
p.then(res=>{
    console.log(res)  // 6
},err=>{
    console.log(err)
})

// 1
// lazy

异步下的 Promise

const p = new Promise( // 1
    function (resolve, reject) {
        console.log('hello')  // 2
        setTimeout(function () { // 3
            reject('tomato'); // 5
        }, 2000)
    }
);

// 4 这里注意一下,绑定回调函数 先与 状态改变
p.then(res => {
    console.log(res)
}, err => {
    console.log('1',err)
})

// hello
// '1'  tomato

4.promise实例上的方法

1.then

代码示例

const p = new Promise(
    function (resolve, reject) {
        setTimeout(function () {
            resolve('tomato');
        }, 2000)
    }
);

p.then(res => {
    console.log('1',res)
}, err => {
    console.log('2',err)
})

// 1 tomato 

注意事项

  1. then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。
  2. 两个函数都是可选的,不一定要提供。
  3. then方法返回的是一个新的Promise实例

2.catch

代码示例

const p = new Promise(
    function (resolve, reject) {
        setTimeout(function () {
            reject('tomato');
        }, 2000)
    }
);

p.catch(err => {
    console.log(err)
})

// tomato

注意事项

  1. catch方法可以接受一个回调函数作为参数。

  2. 回调函数触发的方式有:

    • Promise对象的状态变为rejected时调用。

    • then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获。

    • new实例中的匿名函数运行报错,也会进catch

  3. catch方法返回的也是一个新的Promise实例

  4. catch就是基于then方法变化来的:

    .then(null, rejection).then(undefined, rejection)实现的

3.finally

代码

// 1.finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});


// finally本质上是then方法的特例。
promise
.finally(() => {
  // 语句
});

// 等同于
promise
.then(
  result => {
    // 语句
    return result;
  },
  error => {
    // 语句
    throw error;
  }
);

5.Promise构造函数上的方法

1. Promise.all()

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

Promise.all(
    [
        Promise.resolve('2'),
        Promise.resolve('3'),
        Promise.resolve('4')
    ]
)

上面代码中,Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。

p的状态由p1、p2、p3决定,分成两种情况。

(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

2. Promise.race()

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

const p = Promise.race([
    2,
    new Promise(function (resolve, reject) {
        setTimeout(() => reject(new Error('request timeout')), 5000)
    })
]);

console.log(p)

Promise.race()方法的参数与Promise.all()方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve()方法,将参数转为 Promise 实例,再进一步处理。
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

3. Promise.allSettled()

Promise.all()方法只适合所有异步操作都成功的情况,如果有一个操作失败,就无法满足要求。

为了解决这个问题,ES2020 引入了Promise.allSettled()方法,用来确定一组异步操作是否都结束了(不管成功或失败)。所以,它的名字叫做”Settled“,包含了”fulfilled“和”rejected“两种情况。

返回值的状态只可能为成功;

返回值的结果呢是根据传递来的数据依次排序的,其次成功则status=fulfilled,失败则status=rejected

 async function tomato() {
            let p = await Promise.allSettled(
                ['2',
                    new Promise(function (resolve, reject) {
                        reject('error')
                        setTimeout(() => reject('error'), 5000)
                    })]
            )
            console.log(p) 
            /* 
            PromiseState:'fulfilled'
            PromiseResult:[
                {
                    "status": "fulfilled",
                    "value": "2"
                },
                {
                    "status": "rejected",
                    "reason": "error"
                }
            ]
            */
        }

        tomato()

4. Promise.any()

ES2021 引入了Promise.any()方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。

只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

Promise.any()跟Promise.race()方法很像,只有一点不同,就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。

5. Promise.resolve()

作用:
将现有对象转为 Promise 对象

Promise.resolve()方法的参数分成四种情况。

(1)参数是一个 Promise 实例

如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。

(2)参数是一个thenable对象

thenable对象指的是具有then方法的对象,比如下面这个对象。

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

Promise.resolve()方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法。

(3)参数不是具有then()方法的对象,或根本就不是对象

如果参数是一个原始值,或者是一个不具有then()方法的对象,则Promise.resolve()方法返回一个新的 Promise 对象,状态为resolved。

const p = Promise.resolve('Hello');

p.then(function (s) {
  console.log(s)
});
// Hello

(4)不带有任何参数
Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。
所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve()方法。

6. Promise.reject()

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。

相关博客

  1. 14_2.14_2.手写Promise
  2. 14_3.JS中的错误怎么处理? try catch finally throw

    3.async await (晚点写)

参考博客

end

  • 写到这里我感觉 Promise就是一个构造函数,显式原型上和函数本身有一些方法。用来解决异步问题。当然啊要结合async await用起来也很舒服。
  • 不过如此,当然后面最好看看面试题怎么考的,后续写写这里。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

lazy_tomato

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

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

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

打赏作者

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

抵扣说明:

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

余额充值