什么是promise
Promise是异步编程的一种解决方案,比传统的解决方案【回调函数】和【事件】更合理和更强大。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
Promise出现的原因
队列化处理异步代码,支持链式调用,解决回调地狱
Promise的几个关键问题
1. 如何改变 promise 的状态?
- resolve(value): 如果当前是 pending 就会变为 resolved
- reject(reason): 如果当前是 pending 就会变为 rejected
- 抛出异常: 如果当前是 pending 就会变为 rejected
2. 一个 promise 指定多个成功/失败回调函数, 都会调用吗?
当 promise 改变为对应状态时都会调用,改变状态后,多个回调函数都会调用,并不会自动停止
let p = new Promise((resolve, reject) => { resolve('OK');});
///指定回调 - 1
p.then(value => { console.log(value); });
//指定回调 - 2
p.then(value => { alert(value);});
3. 改变 promise 状态和指定回调函数谁先谁后?
- 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调,但无论如何.then里面的回调都是在状态改变以后执行
- 先改状态再指定回调(同步):改变状态 -->指定回调 并马上执行回调
- 先指定回调再改变状态(异步):先指定回调--> 再改变状态 -->改变状态后才进入异步队列执行回调函数
- 如何先改状态再指定回调? -->注意:指定并不是执行
- 在执行器中直接调用 resolve()/reject() -->即,不使用定时器等方法,执行器内直接同步操作
- 延迟更长时间才调用 then() -->即,在.then()这个方法外再包一层例如延时器这种方法
- 什么时候才能得到数据?
- 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
- 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
let p = new Promise((resolve, reject) => {
//异步写法,这样写会先指定回调,再改变状态
setTimeout(() => {resolve('OK'); }, 1000);
//这是同步写法,这样写会先改变状态,再指定回调
resolve('OK');
});
p.then(value => {console.log(value);}, reason => {})
4. promise.then()返回的新 promise 的结果状态由什么决定?
- 简单表达: 由 then()指定的回调函数执行的结果决定
- 详细表达:
- 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常
- 如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值
- 如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
console.log(value);
// 1. 抛出错误 ,变为 rejected
throw '出了问题';
// 2. 返回结果是非 Promise 类型的对象,返回成功的状态和对应结果,有return返回值结果就为该返回值,没有结果就是undefined
return 521;
// 3. 返回结果是 Promise 对象,此 promise 的结果和状态就会成为result的结果和状态
return new Promise((resolve, reject) => {
// resolve('success');
reject('error');
});
}, reason => {
console.warn(reason);
});
5. promise链式调用?
- promise 的.then()返回一个新的promise对象, 可以开启.then()的链式调用
- 通过 .then() 的链式调用串连多个同步/异步任务,这样就能用then()将多个同步或异步操作串联成一个同步队列
let p = new Promise((resolve, reject) => { setTimeout(() => {resolve('OK'); }, 1000); });
p.then(value => {return new Promise((resolve, reject) => { resolve("success"); });})
.then(value => {console.log(value);})//success
.then(value => { console.log(value);})//undefined
6. promise 异常传透
当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调 前面任何操作出了异常, 都会传到最后失败的回调中处理
let p = new Promise((resolve, reject) => {
reject('err')
});
p.then(value => {console.log(value);})
.then(value => { console.log(value);})
.catch(err=>{
console.log('错误捕获: ', err);//直接穿过前面的.then(),执行.catch()
})
7. 中断 promise 链?
当promise状态改变时,他的链式调用都会生效,那如果我们有这个一个实际需求:我们有5个then(),但其中有条件判断,如当我符合或者不符合第三个then条件时,要直接中断链式调用,不再走下面的then,该如何?
- 当使用 promise 的 .then() 链式调用时, 在中间中断, 则不再调用后面的回调函数
- 办法: 在回调函数中返回一个 pendding 状态的promise 对象
let p = new Promise((resolve, reject) => {setTimeout(() => { resolve('OK');}, 1000);});
p.then(value => {
return new Promise(() => {});//中断,有且只有这一个方式
})
.then(value => { console.log(222);})
.then(value => { console.log(333);})
.catch(reason => {console.warn(reason);});
Promise中的常用 API 概述
1. Promise 构造函数: Promise (excutor) {}
executor 会在 Promise 内部立即同步调用,异步操作在执行器中执行,换话说Promise支持同步也支持异步操作
>- executor 函数(立即执行): 执行器 (resolve, reject) =>{}
>- resolve 函数: 内部定义成功时我们调用的函数 value => {}
>- reject 函数: 内部定义失败时我们调用的函数 reason => {}
2. Promise.prototype.then 方法: (onResolved, onRejected) => {}
- onResolved 函数: 成功的回调函数 (value) => {}
- onRejected 函数: 失败的回调函数 (reason) => {}
说明: 指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调 返回一个新的 promise 对象
3. Promise.prototype.catch 方法: (onRejected) => {}
- onRejected 函数: 失败的回调函数 (reason) => {} 说明: .then()的语法糖, 相当于: .then(undefined, onRejected)
- 异常穿透使用:当运行到最后,没被处理的所有异常错误都会进入这个方法的回调函数中
4. Promise.resolve 方法: (value) => {}
(1) value: 成功的数据或 promise 对象
说明: 返回一个成功/失败的 promise 对象,直接改变promise状态
// 如果传入的参数是非promise对象,则返回成功的promise对象
let p3 = Promise.resolve('111')
// 如果传入的参数是promise对象,则返回的结果决定resolve的结果
let p3 = Promise.resolve(new Promise((resolve, reject)=>{
// resolve('OK');//p3为成功
reject('Error')//p3为失败
}));
// console.log(p3)
p3.catch(err=>console.log(err);)
5. Promise.reject 方法: (reason) => {}
- reason: 失败的原因 说明: 返回一个失败的 promise 对象,直接改变promise状态,代码示例同上
//始终返回失败的状态,失败的结果为传入的那个值,如下new Promise((resolve, reject) => { resolve('OK'); })
let p3 = Promise.reject(new Promise((resolve, reject) => { resolve('OK'); }));
6. Promise.all 方法: (promises) => {}
promises: 包含 n 个 promise 的数组
说明: 返回一个新的 promise, 只有所有的 promise 都成功才成功, 只要有一个失败了就直接返回失败状态并且结果为该失败promise结果
let p1 = new Promise((resolve, reject) => { resolve('成功'); })
let p2 = Promise.reject('错误错误错误');
let p3 = Promise.resolve('也是成功')
const result = Promise.all([p1, p2, p3]);
console.log(result); //状态失败,结果'错误错误错误'
7. Promise.race 方法: (promises) => {}
-
promises: 包含 n 个 promise 的数组
-
说明: 返回一个新的 promise, 第一个完成的 promise 的结果状态就是最终返回的结果状态,
-
如p1延时,开启了异步,内部正常是同步进行,所以p2>p3>p1,结果是P2
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
//调用
const result = Promise.race([p1, p2, p3]);
console.log(result);//返回p2的的状态和结果
8. Promise.allSettled 方法:
Promise.allSettled()可以获取数组中每个promise的结果,无论成功或失败
const runAllSettled = async () => {
const successPromise = Promise.resolve('success') // 一个正常返回的 Promise
const failPromise = Promise.reject('fail') // 一个异常返回的 Promise
// 使用 allSettled
const settiled = await Promise.allSettled([successPromise, failPromise, undefined, null])
console.log(settiled)
/* 输出结果如下
[
{status: 'fulfilled', value: 'success'},
{status: 'rejected', reason: 'fail'},
{status: 'fulfilled', value: undefined},
{status: 'fulfilled', value: null},
]
*/
}
runAllSettled()
- 在不支持
Promise.allSettled新特性的环境下实现一个 Polyfill
// 通过 Promise.all 实现 Promise.allSettled
if (!Promise.allSettled) {
Promise.allSettled = function (promises) {
return Promise.all(
promises.map((p) =>
Promise.resolve(p).then(
(value) => ({
status: "fulfilled",
value,
}),
(reason) => ({
status: "rejected",
reason,
})
)
)
);
};
}
Promise的常规写法基础案例:
- 加载文件
// promise参数为函数类型
// 参数resolve, reject同样也得函数类型
const fs = require('fs')
const getFile=(path)=>{
// excutor函数,同步调用
return new Promise((resolve,reject)=>{
fs.readFile(path, (err, data) => {
if (err) reject(err);
resolve(data)
})
})
}
getFile('./1.txt').then((res)=>{
console.log(res.toString(),'res');
}).catch((err)=>{
console.log(err,'err');
})
// 同上,调用node的util.promisify方法简写
const fs = require('fs')
const util=require('util')
const getFile1=util.promisify(fs.readFile)
getFile1('./1.txt').then((res)=>{
console.log(res.toString(),'res');
}).catch((err)=>{
console.log(err,'err');
})
- 举个异步加载图片的栗子
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
const image = new Image();
image.onload = function() {
console.log('图片加载成功')
resolve(image);
};
image.onerror = function() {
reject(new Error(`无法从 ${url} 中加载图片` ));
};
image.src = url;
});
}
loadImageAsync('正确的url') //打印图片加载成功
loadImageAsync('错误的url') //抛出异常