【JS八股文】Promise
讲到Promise就不得不比较一下回调和Promise的区别和关系。
JS实现异步最常用的方式就是使用回调,Promise是ES6中才引入的概念,他主要是为了解决回调存在的一些问题,可以看作回调的升级版。
那回调中存在什么问题,和Promise又有什么区别呢?
回调的问题
1. 信任问题
假设我去奶茶店买珍珠奶茶,我觉得珍珠不够多,想加一份珍珠。于是我跟店员说我加一份珍珠(回调,在奶茶做好后执行),然后服务员帮我做好标记,我付好钱之后,就等着开心的喝奶茶了。大部分情况下,我是可以愉快的获得一份加了珍珠的珍珠奶茶的,但有时也会有忘记加的情况(别问我为什么知道,怨念..),这就是回调可能存在的信任问题,我们不能确定你的回调函数会如何执行,执行几次,以及会不会执行。
// 伪代码
function buyMilkTea() {
// 制作奶茶,接收一个加料回调
doMilkTea(addSubject);
// 加料
addSubject() {
// 加一份珍珠
}
}
Promise是如何解决信任问题的呢?
Promise和回调不同,他是通过分离回调(一个成功通知,一个错误通知),告诉我他完成了(只会告诉一次),接下来要执行什么,我自己来决定(决定权放在我这)。
还是买奶茶的例子,我付好钱,等待叫号。会有两种结果:
- 叫到我的号,奶茶做好了(成功通知)。这时我可以再买一份珍珠自己加(得到通知后,自己决定后面干嘛)
- 叫到我的号,但是因为原料不足,我的奶茶做不出来(失败通知)。这时我可以选择换一个品类,或者退钱
在回调中,上面的两种情况是写在一个回调函数中的,这样分开写成功和失败的逻辑,也让代码更清晰。
// 伪代码
new Promise((resolve, reject) => {
buyMilkTea();
resolve(); // 通知叫号
}).then(
res => { // 成功回调
addSubject(); // 买一份珍珠,自己加
},
error => { // 失败回调
// 退钱
}
)
2. 回调地狱
回调是异步执行的,不能保证回调函数的执行顺序。因此为了保证回调函数的执行顺序,就会使用回调中嵌套回调一层层嵌套下去,实现我们想要的执行顺序。想想这种代码就非常的头疼...
// 伪代码,大概模拟下回调地狱的情况。。。
getUp(function() {
brushTeeth(function() {
haveBreakfast(function() {
goToSchool(function() {
haveLunch()
goHome(function() {
sleep()
})
})
})
makeUp()
lookInTheMirror()
})
})
Promise是如何解决回调地狱的呢?
和信任问题一样,通过只返回一次状态(成功 / 失败),告诉我他做完,接下来要做什么由我来决定。而且Promise的.then中返回会自动创建一个新的Promise来进行链式链接
// 这种形式更符合我们大脑的逻辑
new Promise((resolve,reject) => {
getUp();
resolve();
}).then(res => {
brushTeeth();
haveBreakfast();
makeUp();
lookInTheMirror();
return;
}).then(res => {
goToSchool();
return;
}).then(res => {
haveLunch();
return;
}).then(res => {
goHome();
return;
}).then(res => {
sleep();
return;
});
总结:回调是把方法交给别人去执行,Promise是别人告诉你“我执行完了”,然后自己控制下面执行什么
Promise详解
通过上述的讲解,我们也大致了解了Promise解决的问题和为什么要有Promise。所以Promise就像一个法官,对我们执行的内容进行判决(只会判决一次,不能改判),我们得到判决结果(then中获得),对响应的结果进行处理。我们也相当于一个法官,返回一个结果(又一次判决),下面通过.then还能获得我们的判决结果(实现链式调用)
Promise API
Promise 构造函数
- 必须和
new一起使用 - 必须提供一个函数回调
- 这个函数接受两个函数回调
resolve和rejectreject拒绝这个Promiseresolve既可以完成也可以拒绝
new Promise((resolve, reject) => { // 提供一个函数回调,这个函数有两个回调resolve和reject
// ... 做些事,这里的内容是同步的
// 完成判决
resolve();
// 拒绝判决 两种方式
// resolve(Promise.reject());
// reject();
});
then( )和catch( )
每个
Promise实例都有then()和catch()方法,可以通过这两个方法来处理Promise的判决结果(都是微任务,插入任务队列)。
then()有两个参数,第一个是处理完成结果的回调,第二个是处理错误信息的回调new Promise((resolve, reject) => { resolve(1); // 或者 // reject(2); }).then( res => {}, // 第一个参数,完成结果的回调 error => {} // 第二个参数,处理错误信息的回调 )catch()只有一个参数,处理错误信息的回调。等价于then(null, rejected)new Promise((resolve, reject) => { // resolve(1); // 或者 reject(2); }).catch( error => {} // 处理错误信息的回调 )
Promise.resolve( )和Promise.reject( )
-
Promise.reject( )快速创建已被拒绝的PromisePromise.reject('error') .then( (res) => { // 不会执行 }, error => { // 执行 console.log(error); // `error` }); -
Promise.resolve( )快速创建一个已完成/拒绝的Promise- 传入的是个数据,创建一个已完成的Promise,并返回传入的数据
- 如果传入值是含有then()的对象,会获取then()返回的值,并创建Promise返回这个值
- 传入的是真正的Promise,直接返回这个promise,不会有任何开销
// 传入的是数据 Promise.resolve('fulfilled') .then( (res) => { // 执行 console.log(res); // `fulfilled` }, error => { // 不会执行 }); // 传入的是thenable var obj = { then(callBack) { callBack('33'); } } Promise.resolve(obj) .then( (res) => { console.log(res); // `33` }); // 传入Promise var p1 = new Promise((resolve, reject) => { resolve('33'); }) Promise.resolve(p1) // 原封不动返回p1 .then(res => { console.log(res); // `33` })
通过Promise.resolve()过滤来获得可信任性的值/Promise,完全没有坏处
Promise的特点
then()的回调是微任务(插入任务队列,进行异步调用)- 链式
then()会注册很多微任务,然后顺序调用 then()会自动创建并返回一个新Promisethen()中return一个Promise会替换then()自动生成的Promise- 两个
独立的Promise的回调运行顺序无法预测 - 每个
Promise内只能判决一次(保证回调只会执行一次),重复的判决会被抛弃 Promise只有一个判决值(resolve()/reject())Promise的返回方法只有一个参数(resolve()/reject()只接受一个参数)Promise出现错误,但没有设置处理函数,那么这个错误会顺着then()的链条传播下去,直到这个错误被处理
Promise模式
Promise.all([ ])
数组内的任务全部完成才完成。返回一个
Promise,返回值是一个数组。 有一个被拒绝,Promise.all([ ])就会立即被拒绝,并丢弃其它所有Promise的结果。
// 全部成功
var p1 = Promise.resolve('33');
var p2 = Promise.resolve('66');
Promise.all([p1, p2])
.then(res => {
console.log(res); // ['33', '66']
});
// 有失败成功
var p1 = Promise.resolve('33');
var p2 = Promise.reject('err');
Promise.all([p1, p2])
.then(res => {
console.log('then'); // 不会执行
})
.catch(err => {
console.log(err); // 'err'
});
Promise.race([ ])
众多
Promise赛跑,只有第一个跨过终点线的Promise被响应,其它抛弃(会把数组中所有的Promise都执行)。一旦有一个Promise决议为拒绝,就会拒绝。
注意:
- 至少有一个参赛者,传入空数组,
Promise永远处于pending状态。(永远不要给空数组) - 由多个
Promise、thenable、立即值组成。立即值在竞争中无意义,因为显然第一个获胜。
// 有立即值的竞赛
var p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
});
var p2 = 2;
Promise.race([p1, p2])
.then((res) => {
console.log(res); // 2
});
// 传入空数组
Promise.race([])
.then((res) => {
console.log(res); // 不会执行
}); // Promise {<pending>}
// 有拒绝的竞赛
var p1 = Promise.resolve(1);
var p2 = 2;
var p3 = Promise.reject(3);
Promise.all([p1, p2, p3])
.then(res => {
console.log('then'); // 不会执行
})
.catch(err => {
console.log(err); // 'err'
});
Promise.any([ ])
只需完成一个(忽略拒绝,不需要数组中的Promise全部执行)。注意和race的区别
Promise相关的面试题
手写实现一个Promise.all()
function promiseAll(arr) {
if (arr.length == 0) return;
let res = [];
let idx = 0; // 保证最后一个promise完成后进行返回
return new Promise((resolve, reject) => {
arr.forEach((pro, i) => {
pro.then(val => {
idx++;
res[i] = val; // 保证结果值与传入的promise的顺序一致,确保promise中有异步的情况下也能正常返回
if (idx == arr.length) resolve(res);
}, err => {
reject(err); // 有一个拒绝,就直接拒绝
})
});
})
}
// 测试
var p1 = Promise.resolve(1);
var p2 = Promise.resolve(2);
promiseAll([p1, p2])
.then(res => {
console.log(res); // [1, 2]
});
手写实现一个Promise.race()
function promiseRace(arr) {
// 没有传值报错
if (!arr) throw(TypeError('undefined is not iterable'));
// 传入的是空数组,一直保持pending
if (arr.length == 0) return new Promise((resolve, reject) => {});
// 正常情况
return new Promise((resolve, reject) => {
arr.forEach(p => {
// 运用Promise.resolve()的特性,解析一下p,保证返回promise
Promise.resolve(p).then(res => {
resolve(res);
}, err => {
reject(err);
});
})
});
}
// 测试
var p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
});
var p2 = 2;
promiseRace([p1, p2])
.then((res) => {
console.log(res); // 2
});
如果觉得内容对你有帮助,请点个赞和关注,你们的鼓励是我持续更新下去的动力,比心。
如需转载请注明出处,感恩。
更多内容可先关注GitHub
转载自:https://juejin.cn/post/7094438323149602823