这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战
Promise
Promise介绍
Promise是异步编程的一种解决方案,从语法上来看Promise是一个对象,从它可以获取异步操作的消息,简单来说,它就像是一个容器,里边保存着某个未来结束的事件,通常是获取异步操作的结果。Promise有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败),一旦状态改变就不会在改变,任何时候得到的都是这个结果。Promise状态改变无非两种结果,一种是从pending到fulfilled,一种是从pending到rejected
Promise基本语法
在ES6中规定,Promise对象是一个构造函数,用来生成Promise实例
const promise = new Promise(function(resolve,reject){
if(异步处理结果为成功){
resolve(value)
}else{
reject(error)
}
})
从上面代码中我们可以看出,Promise构造函数接收了一个函数为参数,该函数有resolve和reject两个参数,
resolve函数是当异步操作成功的时候调用,并将异步操作的结果作为参数传递出去,
reject函数是当异步操作失败时调用的,并将错误信息作为参数传递出去,
then
Promise实例生成之后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
promise.then(value=>{
console.log(value)
},error=>{
console.log(error)
})
then方法可以接受两个回调函数作为参数,第一个回调函数就是Promise返回成功的回调函数,第二参数就是Promise返回失败的回调函数
多个then
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
promise.then(value=>{
console.log(value)
}).then(value=>{
console.log(value)
}).then(value=>{
console.log(value)
})
catch
我们知道Promise对象除了then方法,还有一个catch方法,它是做什么用的呢?其实它和then的第二个参数一样,用来指定reject的回调。用法是这样:
promise.then(value => {
console.log('resolved',value);
}).catch(err => {
console.log('rejected',err);
});
效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。请看下面的代码:
promise.then((data) => {
console.log('resolved',data);
console.log(error); //这里的error未定义
}) .catch((err) => {
console.log('rejected',err);
});
在resolve的回调中,我们console.log(error);而error这个变量是没有被定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不往下运行了。但是在这里,会得到这样的结果:
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。一般来说,不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法,更接近同步的写法(try/catch)。因此,建议总是使用catch()方法,而不使用then()方法的第二个参数。
finally
finally方法用于指定不管Promise对象最后的状态如何,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数
promise.then(value => {
状态为fulfilled 执行
}).catch(error => {
状态为rejected 执行
}) .finally(() => {
不管状态为什么都会执行
});
finally不接收任何参数,这就意味着没办法知道Promise返回的状态如何,那么,finally方法里边的操作与状态无关,不依赖Promise的执行结果
all
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
//三个状态都成功
const promise1 = new Promise((resolve,reject)=>{
resolve('success1')
})
const promise2 = new Promise((resolve,reject)=>{
resolve('success2')
})
const promise3 = new Promise((resolve,reject)=>{
resolve('success3')
})
const p = Promise.all([promise1,promise2,promise3])
p.then(data=>{
console.log(data); //三个都成功则成功
}, error=>{
console.log(error) // 只要有失败,则失败
})
这是上边代码执行出来的结果,现在让我看看有一个失败的会怎样
//三个状态都成功
const promise1 = new Promise((resolve,reject)=>{
resolve('success1')
})
const promise2 = new Promise((resolve,reject)=>{
reject('error')
})
const promise3 = new Promise((resolve,reject)=>{
reject('error1')
})
const p = Promise.all([promise1,promise2,promise3])
p.then(data=>{
console.log(data); //三个都成功则成功
}, error=>{
console.log(error) // 只要有失败,则失败
})
通过上面的代码可以看出,p的状态是由promise1 promise2 promise3决定的,分为两种情况:
- 只有promise1 promise2 promise3的状态都为fulfilled的时候,那么p的状态也为fulfilled,此时promise1 promise2 promise3的返回值组成一个数组,传给p的回调函数
- 只要 promise1 promise2 promise3 之中有一个被 rejected,p的状态就变成 rejected,此时第一个被reject 的实例的返回值,会传递给p的回调函数。
race
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const promise1 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('成功');
}, 2000);
})
const promise2 = new Promise((resolve,reject)=>{
setTimeout(() => {
reject('失败');
}, 5000);
})
const p = Promise.race([promise1,promise2])
p.then(data=>{
console.log(data);
}, error=>{
console.log(error)
})
从上边代码的结果可以看出,只要promise1 promise2 之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
resolve
有时需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用。
resolve方法的参数分为四种情况:
-
参数是一个Promise实例的
resolve方法不做任何的处理,原封不动返回
-
参数是一个具有then方法的对象
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function (value) {
console.log(value); // 42
});
resolve方法会将这个对象转为Promise对象,然后立即执行对象里边的then方法 3. 参数不是具有then()方法的对象,或根本就不是对象
const p = Promise.resolve('Hello');
p.then(function (s) {
console.log(s)
});
// Hello
如果参数是一个原始值,或者是一个不具有then方法的对象,则resolve方法返回一个新的 Promise 对象,状态为resolved。
- 不带参数的
const p = Promise.resolve();
p.then(function () {
// ...
});
resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。
所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用resolve方法。
reject
reject方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
上面代码生成一个 Promise 对象的实例p,状态为rejected,回调函数会立即执行。
reject方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。
async await
async/await是JavaScript为了更好的解决异步问题而提出的一种解决方案,许多人将其称为异步的终极解决方案,下面让我们一起来看一下async/await
基本用法
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
使用注意点
await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
- 多个
await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
let foo = await getFoo();
let bar = await getBar();
上面代码中,getFoo和getBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。
可以写成这样:
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
await命令只能用在async函数之中,如果用在普通函数,就会报错
async function dbFuc(db) {
let docs = [{}, {}, {}];
// 报错
docs.forEach(function (doc) {
await db.post(doc);
});
}
优雅的错误处理方法
上边我们也说到async返回一个Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中,如果是有多个异步操作,需要对每个异步返回的error错误进行不同的处理,我们可以这样写:
const fetchDataA = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fetch data is A')
}, 1000)
})
}
const fetchDataB = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fetch data is B')
}, 1000)
})
}
const fetchDataC = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fetch data is C')
}, 1000)
})
}
(async () => {
try {
const dataA = await fetchDataA()
console.log('dataA is ->', dataA)
} catch(err) {
console.log('err is ->', err)
}
try {
const dataB = await fetchDataB()
console.log('dataB is ->', dataB)
} catch(err) {
console.log('err is ->', err)
}
try {
const dataC = await fetchDataC()
console.log('dataC is ->', dataC)
} catch(err) {
console.log('err is ->', err)
}
})()
这样处理也不是不可以,但是代码里乍一看全是try...catch,看着不是那么优雅,这时我们是不是也想只用一个try...catch
// ... 这里 fetch 函数省略
(async () => {
try {
const dataA = await fetchDataA()
console.log('dataA is ->', dataA)
const dataB = await fetchDataB()
console.log('dataB is ->', dataB)
const dataC = await fetchDataC()
console.log('dataC is ->', dataC)
} catch(err) {
console.log('err is ->', err)
// 难道要定义 err 类型,然后判断吗??
/**
* if (err.type === 'dataA') {
* console.log('dataA err is', err)
* }
* ......
* */
}
})()
如果是这样的话那我们岂不是要写一对if else的判断,这时候我们在想async返回的是Promise对象,那么我们就可以用then方法
(async () => {
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fetch data is me')
}, 1000)
})
}
const data = await fetchData().then(data => data ).catch(err => err)
console.log(data)
})()
在上面写法中,如果 fetchData 返回 resolve 正确结果时,data 是我们要的结果,如果是 reject 了,发生错误了,那么 data 是错误结果,这样我们就又要判断了,显然不太合适。
(async () => {
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fetch data is me')
}, 1000)
})
}
const [err, data] = await fetchData().then(data => [null, data] ).catch(err => [err, null])
console.log('err', err)
console.log('data', data)
// err null
// data fetch data is me
})()
这样是不是好很多了呢,但是问题又来了,不能每个 await 都写这么长,写着也不方便也不优雅,并且会有很多的重复代码
(async () => {
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('fetch data is me')
}, 1000)
})
}
// 抽离成公共方法
const awaitWrap = (promise) => {
return promise
.then(data => [null, data])
.catch(err => [err, null])
}
const [err, data] = await awaitWrap(fetchData())
console.log('err', err)
console.log('data', data)
// err null
// data fetch data is me
})()
将对 await 处理的方法抽离成公共的方法,在使用 await 调用awaitWrap 这样看起来是不是就好多了
结语
上面就是我关于Promise和async/await理解的记录,
以上内容估计还有许多错误之处,如果掘金上的朋友有看到,还望不吝指出!
同时我也希望大家通过观看这篇文章能够有所收获!谢谢您的观看!
参考文献:
https://es6.ruanyifeng.com/
https://juejin.cn/post/6844903767129718791