这次一定,弄懂Promise!!!

1,050 阅读10分钟

这次一定,弄懂Promise!!!

🙋 Hello,I'm IamZJT!
✍️ 一名菜鸟前端开发工程师!

📦 Github地址:iamzjt-front-end
🖐️ 欢迎 点赞 star,期盼与您并肩前行...

开始之前

我相信很多朋友都有快速浏览的习惯,大概看一下,看着看着就跳出去了。

如果真的想好好学一下promise,我建议:先上个厕所,倒杯水,排除掉其他事情,然后坐下来,咱们一点一点来看,这次一定,弄懂promise,加油!

或者直接跳到最后,评论一句:下次一定!

image.png

哈哈哈哈,不开玩笑了,咱们开始...

一. 经常遇到

大家面试的时候,会不会被经常遇到几个问题?

ES6知道吗? 介绍一下ES6语法你经常用到哪些吧? 说一下Promise

一旦遇到这个问题,开口第一句经常说的就是:Promise是异步编程的一种解决方案...

说到这里,就涉及到为什么会有Promise(期约),当然是因为有异步操作了啊!

在此之前,我们需要在移动操作拿到一个数据之后,再进行另外一项操作,最常用的方法就是callback回调函数了。

举个栗子:

console.log(111);
function fn(val, cb) {
    setTimeout(() => {
        console.log(val);
        cb && cb();
    }, 1000)
}
fn(222, () => console.log(333));

// 结果:1111  2222  3333

然而开发过程中,往往异步返回值又依赖另一个异步返回值,回调就进一步复杂,代码中,就要求嵌套回调。

再举个栗子:

console.log(111);
function fn(val, cb) {
    setTimeout(() => {
        console.log(val);
        cb && cb(val * 2);
    }, 1000)
}
fn(2222, val => fn(val));

// 结果:1111  2222  4444

但是在这种异步操作越来越多的时候,就会一层嵌套一层,非常多层的嵌套回调,简直就是噩梦,更别说去维护了,这也就是“回调地狱”。

而我们经常说的promise就是最先由社区提出来的,用来解决回调地狱,后面ES6写进了语言标准,统一了语法。

二. 邂逅promise

“邂逅”这个词是跟coderwhy老师学的,他每次讲解一个新技术,就很喜欢用邂逅,他讲课真的很棒,细致且严谨,此处非常推荐!!!

好,回归正题。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

如前所述,Promise 是一个有状态的对象,可能处于如下 3 种状态之一:

  • penging (进行中)
  • fulfilled (已成功)
  • rejected (已失败)

pending 是最开始的状态,而这三种状态只有两种变化的可能性,且变化状态不可逆。

  1. 执行成功:pending --> fulfilled
  2. 执行失败:pending --> rejected

promise流程图.png

当然,要注意的是Promise的状态是私有的,不能直接通过 JavaScript 检测到,也不能被外部 JavaScript 代码修改,Promise故意将异步行为封装起来,从而隔离外部的同步代码。

补充一下:Promise还有另外一些特点,也可以说是缺点:

  1. 一旦执行,不可取消;
  2. 无法得知执行进度,即:处于pengding状态时,不知道是刚刚开始还是快要结束;

总上所述,其实说白了:Promise就是异步操作的同步写法。

三. Promise基本用法

众所周知,Promise对象是个构造函数,那么就可以通过new来进行实例化。

那我们就先来创造一个Promise实例,看看具体是个啥?

let p = new Promise((resolve, reject) => {
    console.log(1111);
    setTimeout(() => {
        console.log(2222);
        resolve('success');
        // reject('fail');
    }, 1000)
});

console.log(p);

// 结果:
// 1111  
// Promise {<pending>}
//     [[Prototype]]: Promise
//     [[PromiseState]]: "fulfilled"
//     [[PromiseResult]]: "success" 
// 2222

从上面的代码可以知道:通过Promise构造函数创建实例化对象。其中,异步操作写在函数体中。

  • Promise构造函数参数是有两个参数,resolvereject,这两个参数都是函数。

  • resolve()函数是在异步操作执行成功后调用,resolve的参数是Promise执行的Result。

  • reject()函数是异步操作执行失败时调用,参数是异步操作所报的错。

    四. Promise的实例方法

    上文提到,Promise状态内部私有且故意将异步操作封装起来,与外部代码隔离。那么如何与外界沟通呢?Promise的实例方法就是连接外部同步代码与内部异步代码之间的桥梁。这些方法可以访问异步操作返回的数据,执行成功和失败的结果。

1. Promise.prototype.then()

then方法最多接收两个参数,onResolved()和onRejected(),分别指定的是fulfilled状态和rejected状态的回调函数,而参数则是resolve()和reject()传递过来的。如下:

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('success');
        // reject('fail');
    }, 1000)
});

p.then(function (res) {
    console.log('成功回调', res);
}, function (err) {
    console.log('失败回调', err);
})

// 执行成功-结果:成功回调 success
// 执行失败-结果:失败回调 fail

而then()方法有三种返回值:

第一种:不return,则默认返回一个fulfilled状态的promise对象
let p2 = p.then(function () {

})
console.log(p2);

// 结果:
// Promise {<pending>}
//     [[Prototype]]: Promise
//     [[PromiseState]]: "fulfilled"
//     [[PromiseResult]]: undefined
第二种:return非promise对象;默认返回promise对象,并把返回值当作参数放进去
let p3 = p.then(function () {
    return 1111;
})
console.log(p3);

// 结果:
// Promise {<pending>}
//     [[Prototype]]: Promise
//     [[PromiseState]]: "fulfilled"
//     [[PromiseResult]]: 1111
第三种:return new Promise对象,那返回的肯定是个Promise对象,这没啥好说的
let p4 = p.then(function () {
    return new Promise((resolve, reject) => {
        resolve();
    })
})
console.log(p4);

// 结果:
// Promise {<pending>}
//     [[Prototype]]: Promise
//     [[PromiseState]]: "fulfilled"
//     [[PromiseResult]]: undefined

由上不难看出,无论何种情况都是会返回一个新的Promise对象,那么新的Promise对象也有then方法,那就then的链式操作就来了。

function p(val) {
    return new Promise((resolve, reject) => {
        resolve(val);
    })
}

p(1111).then(val => {
    console.log(val);
    return p(2222);
}).then(val => {
    console.log(val);
    return p(3333);
}).then(val => {
    console.log(val);
    return p(4444);
}).then(val => {
    console.log(val);
})

// 结果:1111  2222  3333  4444

2. Promise.prototype.catch()

看到.catch()的第一感觉就是用来捕获异常的,其实没错,和.then()方法的第二个参数onRejected()一个意思。

Promise.prototype.catch()方法其实就是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(1111);
        reject(1);
    }, 1000)
})

p.catch(err => console.log(err));
p.then(null, err => console.log(err));
p.then(undefined, err => console.log(err));

// 结果: 1111  1  1  1

一般总是建议,Promise 对象后面要跟catch()方法,这样可以处理 Promise 内部发生的错误。catch()方法返回的还是一个 Promise 对象,因此后面还可以接着调用then()方法。

3. Promise.prototype.finally

finally,顾名思义,不论结果如何,最后总要执行的方法。

就像我们,纷飞的流年里,总有一份守候,一份执着在为某个人而开启着,那段路那段情既然认定了,便会义无反顾的坚持到最后。无论结局如何,心中坚守的信念始终都未曾动摇。扯远了...

继续举个栗子,我一直坚信想要技术好,代码敲的少不了,拿例子说话,let's go

let p = new Promise((resolve, reject) => {
    resolve(1111);
    // reject(2222);
})

p.then(val => {
    console.log('执行成功:' + val);
}).catch(err => {
    console.log('执行失败:' + err);
}).finally(() => {
    console.log('执行完成');
})

// resolve(1111) 结果:
// 执行成功:1111
// 执行完成

// reject(2222) 结果:
// 执行失败:2222
// 执行完成

其实仔细想想,不论怎样都执行,那意思不就是:不论是fulfilled状态,还是rejected状态,都执行。

那这么看来,finally本质上是then方法的特例。

既然如此,那就实现一下:

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    val  => P.resolve(callback()).then(() => val),
    err => P.resolve(callback()).then(() => { throw err })
  );
};

上面代码中,不管前面的 Promise 是fulfilled还是rejected,都会执行回调函数callback

从上面的实现还可以看到,finally方法总是会返回原来的值。

4. Promise.all() 和 Promise.race()

Promise 类提供两个将多个Promise实例组合成一个新的Promise实例的静态方法:Promise.all()和 Promise.race()。

4.1 Promise.all()

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(1111);
        resolve(1);
    }, 2000)
})
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(2222);
        resolve(2);
    }, 3000)
})
let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(3333);
        resolve(3);
        // reject(3);
    }, 1000)
})

let p = Promise.all([p1, p2, p3]);
console.log(p);

// p3 resolve(3) 结果:
// Promise {<pending>}
//     [[Prototype]]: Promise
//     [[PromiseState]]: "fulfilled"
//     [[PromiseResult]]: [1, 2, 3]
// 3333
// 1111
// 2222

// p3 reject(3) 结果:
// Promise {<pending>}
//     [[Prototype]]: Promise
//     [[PromiseState]]: "rejected"
//     [[PromiseResult]]: 3
// 3333
// Uncaught (in promise) 3
// 1111
// 2222

p的状态由p1p2p3决定,分成两种情况。

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

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

4.2 Promise.race()

race:竞速、赛跑的意思;

结合上面的.all()方法,不难猜出:p的状态由最先完成的promise实例状态决定,即:p1p2p3一起赛跑,谁最先改变状态,那么p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

说了一堆我也看不懂啊,说的啥啊,举个栗子啊。。。

天不生我键盘侠,码道万古如长夜,码来...

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(1111);
        resolve(1);
    }, 2000)
})
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(2222);
        resolve(2);
    }, 3000)
})
let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(3333);
        resolve(3);
        // reject(3);
    }, 1000)
})

Promise.race([p1, p2, p3]).then(res => {
    console.log(res);
})

// p3 resolve(3) 结果:
// Promise {<pending>}
//     [[Prototype]]: Promise
//     [[PromiseState]]: "fulfilled"
//     [[PromiseResult]]: undefined
// 3333
// 3
// 1111
// 2222

// p3 reject(3) 结果:
// Promise {<pending>}
//     [[Prototype]]: Promise
//     [[PromiseState]]: "rejected"
//     [[PromiseResult]]: 3
// 3333
// Uncaught (in promise) 3
// 1111
// 2222

分析一波:

  1. p3 resolve(3)

p3最先执行完成,打印3333,然后Promise.race([p1, p2, p3])这个新的promise实例根据第一个完成的p3来改变自身状态,变成fulfilled,然后打印传递过来的res,也就是3,然后接着p1完成,打印1111p2完成,打印2222

  1. p2 reject(3)

此处唯一不同的是,执行失败,抛出异常。

以上就是promise的介绍以及一下常用的promise实例方法。

如有错误,欢迎指出,在下必定细听改正。

五. 总结

以前自己只是知道promise的大概概念,有哪些状态,可是对于有哪些常见的实例方法,却并不是很熟悉,这次重新学习一遍promise以后,对promise有了一个初步的认识,写下这篇文章,一方面是总结,另一方面是记录学习过程。

看到三心哥、小卢哥、寒草哥的文章,既有技术深度,又通俗易懂,真的非常佩服。想想自己也没什么技术,也没什么才华,硬着头皮,慢慢的写下了在掘金的第一篇文章,借着写文章来让自己继续保持学习。

学习是痛苦的,但是能把学习变的不那么痛苦,甚至是快乐,那是本事。

第一篇掘金的文章到这也就结束了...👏👏👏

我并非明珠,所以需要刻苦雕琢。冲...

image.png

ps

🎯 如果您看到这里,请不要走开。
🎉 这是一个早起俱乐部:三更灯火五更鸡

⭐️ 寻找 志同道合 的小伙伴,我们一起 早起