这次一定,弄懂Promise!!!
🙋 Hello,I'm IamZJT!
✍️ 一名菜鸟前端开发工程师!📦 Github地址:iamzjt-front-end。
🖐️ 欢迎点赞➕star,期盼与您并肩前行...
开始之前
我相信很多朋友都有快速浏览的习惯,大概看一下,看着看着就跳出去了。
如果真的想好好学一下promise,我建议:先上个厕所,倒杯水,排除掉其他事情,然后坐下来,咱们一点一点来看,这次一定,弄懂promise,加油!
或者直接跳到最后,评论一句:下次一定!
哈哈哈哈,不开玩笑了,咱们开始...
一. 经常遇到
大家面试的时候,会不会被经常遇到几个问题?
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 是最开始的状态,而这三种状态只有两种变化的可能性,且变化状态不可逆。
- 执行成功:pending --> fulfilled
- 执行失败:pending --> rejected
当然,要注意的是Promise的状态是私有的,不能直接通过 JavaScript 检测到,也不能被外部 JavaScript 代码修改,Promise故意将异步行为封装起来,从而隔离外部的同步代码。
补充一下:Promise还有另外一些特点,也可以说是缺点:
- 一旦执行,不可取消;
- 无法得知执行进度,即:处于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构造函数参数是有两个参数,resolve和reject,这两个参数都是函数。 -
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的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
4.2 Promise.race()
race:竞速、赛跑的意思;
结合上面的.all()方法,不难猜出:p的状态由最先完成的promise实例状态决定,即:p1、p2、p3一起赛跑,谁最先改变状态,那么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
分析一波:
- p3 resolve(3)
p3最先执行完成,打印3333,然后Promise.race([p1, p2, p3])这个新的promise实例根据第一个完成的p3来改变自身状态,变成fulfilled,然后打印传递过来的res,也就是3,然后接着p1完成,打印1111,p2完成,打印2222;
- p2 reject(3)
此处唯一不同的是,执行失败,抛出异常。
以上就是promise的介绍以及一下常用的promise实例方法。
如有错误,欢迎指出,在下必定细听改正。
五. 总结
以前自己只是知道promise的大概概念,有哪些状态,可是对于有哪些常见的实例方法,却并不是很熟悉,这次重新学习一遍promise以后,对promise有了一个初步的认识,写下这篇文章,一方面是总结,另一方面是记录学习过程。
看到三心哥、小卢哥、寒草哥的文章,既有技术深度,又通俗易懂,真的非常佩服。想想自己也没什么技术,也没什么才华,硬着头皮,慢慢的写下了在掘金的第一篇文章,借着写文章来让自己继续保持学习。
学习是痛苦的,但是能把学习变的不那么痛苦,甚至是快乐,那是本事。
第一篇掘金的文章到这也就结束了...👏👏👏
我并非明珠,所以需要刻苦雕琢。冲...
ps
🎯 如果您看到这里,请不要走开。
🎉 这是一个早起俱乐部:三更灯火五更鸡!
⭐️ 寻找 志同道合 的小伙伴,我们一起 早起。