我正在参加「掘金·启航计划」 \
1. Promise 简单了解
Promise(中文翻译:承诺)主要用于异步计算
- 可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果。
- 可以在对象之间传递和操作promise,帮助我们处理队列。
异步回调的问题:
-
之前处理异步是通过纯粹的回调函数的形式进行处理,很容易进入到回调地狱中,剥夺了函数 return 的能力。虽然问题可以解决,但是难以读懂,且维护困难,稍有不慎就会踏入:回调地狱 - 嵌套层次深,不好维护。
-
而 promise 是一个对象,对象和函数的区别就是对象可以保存状态,函数不可以(闭包除外) 并未剥夺函数 return 的能力,因此无需层层传递 callback,进行回调获取数据。且代码风格容易理解,便于维护多个异步等待合并便于解决
2. Promise 详解:
/ 创建promise对象
new Promise((resolve,reject)=>{
// 第一次执行promise
console.log('第一层promise')
// 第二次执行promise
resolve('这是第二层promise')
// 定义报错信息;报错时输出这一行
reject('这是一个错误')
// 也可以主动抛出异常来控制报错时输出什么
throw "抛出一个异常"
// 成功时执行
}).then(res => {
// 输出执行成功的数据
console.log(res)
// 执行 action
return action("使用函数统一控制嵌套函数",4000)
// 报错时提醒
},err=>{
console.log(err)
})
2.1. 实例:
执行结果:定时器每隔一秒执行一次,promise 一次性执行完
多个promise嵌套:
结果:
第二种:效果一样
第三种嵌套方式:效果都一样,项目开发时按需使用
2.2. Promise 里嵌套定时器
简单嵌套:
效果:
使用函数的方式使用:
结果:
3. 使用 Promise 加载图片
3.1 传统方案
传统图片加载方式,会导致地狱回调,让代码及其不优雅,且难以维护。
let url1 = "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3293635140,3955114282&fm=26&gp=0.jpg"
let url2 = "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1019333328,1858047370&fm=26&gp=0.jpg"
let url3 = "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=4226468334,723383513&fm=26&gp=0.jpg"
let oImg1 = new Image()
oImg1.onload = function() {
console.log('img1加载完毕')
let oImg2 = new Image()
oImg2.onload = function() {
console.log('img2加载完毕')
let oImg3 = new Image()
oImg3.onload = function() {
console.log('img3加载完毕')
console.log('全部加载完毕')
}
oImg3.src = url3
}
oImg2.src = url2
}
oImg1.src = url1
3.2 使用 promise 加载图片
function loadImg(url) {
let img = new Image()
img.src = url
return new Promise((resolve, reject) => {
img.onload = () => {
console.log(url)
resolve()
}
img.onerror = (e) => {
reject(e)
}
})
}
// 图片
let url1 = 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3293635140,3955114282&fm=26&gp=0.jpg'
let url2 = "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1019333328,1858047370&fm=26&gp=0.jpg"
let url3 = "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=4226468334,723383513&fm=26&gp=0.jpg"
// 解决回调地狱问题,使代码变得清晰
loadImg(url1).then(() => {
return loadImg(url2)
}).then(() => {
return loadImg(url3)
})
4. Promise 方法
4.1 Promise.all()
成功的时候返回的是一个结果数组,而失败的时候则返回最先被 reject 失败状态的值。且一旦有失败,其他请求成功的结果也不会返回。
运行结果:两秒过后同时输出
4.2 Promise.race()
返回第一个执行完毕的 promise。
// promise1
var p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, "one")
})
// promise2
var p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "two")
})
Promise.race([p1, p2]).then(value => {
// 输出:two
// 虽然两个 promise 都执行完成了,但很显然 p2 更快。
console.log(value)
})
//再来看个案例
// promise3
var p3 = new Promise((resolve, reject) => {
setTimeout(reject, 100, "three")
})
// promise4
var p4 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, "four")
})
Promise.race([p4, p3]).then(value => {
// 输出:three
// 虽然 p3 是一个错误的 promise,但 race() 方法永远只返回第一个执行完成的 promise。
console.log(value)
})
4.3 Promise.allSettled()
将传入的 promise 集合执行完成后的状态返回出去。
const promise1 = Promise.resolve(3)
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'))
Promise.allSettled([promise1, promise2]).
// 输出:fulfilled 3 undefined
// rejected undefined foo
then((results) => results.forEach((result) => console.log(result.status, result.value, result.reason)))
4.4 Promise.done()
done() 方法在 promise 里是不存在的,是大佬自己写的在使用 promise 时的一个很实用的方法。
done() 保证捕捉到任何可能出现的错误,并向全局抛出。
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected).catch(function (reason) {
// 抛出一个全局错误
setTimeout(() => {
throw reason
}, 0)
})
}
4.5 Promise.finally()
promise 之前并没有这个方法,是最近两年 ES 更新的新语法,所以在浏览器兼容性上并不是太好。早年是大佬在 promise 的原型上添加了 finally() 这个方法来手动实现的。
在 promise 全部执行完毕后做一些统一处理。无论成功还是失败都会执行的处理。并且因为无法知道 promise 的最终状态,所以 finally() 的回调函数中不接受任何参数。它只能用于最终结果无论如何都必须要执行的情况。
const p = new Promise((resolve, reject) => {
if(Math.random() > 0.5){
resolve('resolve')
}
reject('reject')
})
p.then((res) => {
console.log(res);
}).catch((err) => {
console.error(err);
}).finally(() => {
// 无论 promise 执行结果如何,都会执行这里的 执行完毕
console.log('执行完毕')
})
// 假设执行成功,则会输出:resolve 执行完毕
// 执行失败,则会输出:reject 执行完毕
实现 promise.finally() 方法
Promise.prototype.finally = function (callback) {
let P = this.constructor
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => throw reason)
)
}
4.6 Promise.any()
执行传入的所有 promise 并返回第一个执行成功的。只要第一个 promise 已成功返回,那么之后的所有 promise 都不在执行。
const p = [
Promise.resolve('result A'),
Promise.resolve('result B'),
Promise.resolve('result C'),
]
promise.any(p).then((value) => {
// 输出:result A
console.log('value: ', value)
})
5. 扩展
1. 实现异步并发数量控制
// 异步控制并发数
// urls:请求列表。
// limit:限制发送数。
function limitRequest(urls = [], limit = 3) {
return new Promise((resolve, reject) => {
// 获取请求总数
const len = urls.length;
// 当前执行次数
let count = 1;
// 同步启动limit个任务
while (limit > 0) {
start();
// 并发数减一
limit -= 1;
}
// 实现方法
function start() {
// 从数组中拿取第一个任务
const url = urls.shift();
if (url) {
axios.post(url).finally(() => {
// 判断当前执行次数是否和请求总数一致
if (count == len) {
// 最后一个任务完成时,调用 resolve() 退出 Promise
resolve();
} else {
// 完成之后,启动下一个任务
count++;
start();
}
});
}
}
});
}