JavaScript的Promise和异步函数 async-await
概述
promise是js的异步请求机制,再ES6时代引入官方的js文档,解决之前的回调函数引起的回调地狱。
promise可以分为两部分去理解 一个是promise构造函数本身,二是promise的一系列方法。
promise构造函数
首先要明确的一点是,promise本身是一个构造函数,即使用 new Promise
的方法生成一个对象,且生成实例时 需要传入一个执行器函数,通常情况如下所示:
new Promise( (resolve, reject) => {
if( statement){
resolve();
} else {
reject();
}
})
我们先从promise构造函数所产生的对象本身开始:
这是输出的一个promise 对象 ,有两个属性:[[PromiseState]]、 [[PromiseResult]]。
[[PromiseState]] 一共有三个值:“pending”、 “fulfilled”、“rejected”,再中文语境下分别代表 未处理,成功,失败。
该属性默认状态为 pending,状态只可 改变一次 。
若新创建一个promise实例,不加任何改变,该属性的输出为 pending
在执行器函数内 使用 rejected或 resolve 更改状态 该属性的输出为 rejected 或 fulfilled
但在执行一次rejected或 resolve后,再次执行rejected或 resolve,结果不会改变:
let result = new Promise((resolve,reject) => {
})
console.log(result);
// [[PromiseState]]: "pending"
//使用rejecte()输出 "rejected"
let result = new Promise((resolve,reject) => {
resolve()
})
console.log(result);
// [[PromiseState]]: "fulfilled""
//两次执行 状态只改变一次
let result = new Promise((resolve,reject) => {
reject()
resolve()//不会改变状态
})
console.log(result);
// [[PromiseState]]: "rejected"
而第二个属性 [[PromiseResult]] 为调用改变状态函数的值,例:
let result = new Promise((resolve,reject) => {
resolve(1);
})
console.log(result);
/*输出结果如下:
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 1*/
所以我们可以简单的使用一下prmoise
let i = 2;
let result = new Promise((resolve,reject) => {
if(i >1){
resolve(i)
} else {
reject(i)
}
})
console.log(result);
/*
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 2
*/
let i = 0;
let result = new Promise((resolve,reject) => {
if(i >1){
resolve(i)
} else {
reject(i)
}
})
console.log(result);
/*
[[Prototype]]: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: 0
*/
总结promise对象本身
使用promise构造函数 最终生成一个包含两个属性的promise对象,该对象的PromiseState属性的值只能更改一次。虽然说 promise是解决异步问题的,但目前为止,promise一直都是同步执行。
promise原型对象上的方法
promise.prototype上一共有三个方法 then,catch,finally
then方法
then方法可以说是最重要的方法,then中的方法是 异步执行 的。它支持传入两个函数,第一个函数处理promise 对象为 fullfilled 状态,第二个函数处理 promise 对象 为rejected状态。
不一定两个函数都要传入,可以用null代替第一个函数,也可以不写第二个函数。但这样没有意义,因为promise的作用就是异步处理。
let i = 2;
let result = new Promise((resolve,reject) => {
if(i >1){
resolve(i)
} else {
reject(i)
}
})
result.then(
() => {console.log('resolve')},
()=> {console.log('reject')}
)
console.log(result);
/*Promise {<fulfilled>: 2}
test.html:81 resolve*/
then方法的执行时机
then中代码是在微任务中执行,微任务在宏任务执行完之后执行(宏任务即主要的js代码)微任务执行后再执行消息列队中的代码,回调函数,计时器,dom监听都会进入消息列队。
再看一道例题:
//请写出输出顺序
const first = () => (new Promise((resolve, reject) => {
console.log(3);
let p = new Promise((resolve, reject) => {
console.log(7);
setTimeout(() => {
console.log(5);
resolve(6);
}, 0)
resolve(1);
});
resolve(2);
p.then((arg) => {
console.log(arg);
});
}));
first().then((arg) => {
console.log(arg);
});
console.log(4);
答案如下:
// => 3
// => 7
// => 4
// => 1
// => 2
// => 5
为什么不是374562呢?
到目前未知,我们已经知道promise构造函数中的代码是同步执行,而then中的代码是异步执行。
那么可以确定前三个输出的值为 374 而当代码执行到 setTimeout(()...
时,会将整体放入到消息列队中,随后执行 resolve(1) 更改promise的状态和值,当执行完 resolve(2);
遇到then方法,将方法放入微任务中 继续往后执行。
宏任务执行完成之后(执行完 console.log(4)
之后)开始执行微任务,也就是首先放入的then方法
p.then((arg) => {
console.log(arg);
});
因为这时p的状态已经被 resolve(1);
所更改 所以输出 1
再执行 first().then((arg)
输出2。当两个then所在的微任务执行完成之后 再执行 消息队列中的 settimeout 函数 输出7 而p的状态已经改变过 由promise的规则 状态只能改变一次 所以 resolve(6) 无效。
then方法的返回值以及链式调用
promise使用链式调用的方法,将多个层级的回调地狱 问题 转化为扁平化的代码:
new Promise( resolve => resolve(100))
.then(then1 => 10)
.then(then2 => console.log(then2))
//10
之所以可以这样做,是因为then也返回一个promise 对象,只要then内部的代码不出错,新的pormise状态就为fullfilled,且return 出去的值就是 新的promise 的值。
若then中的代码出错,则返回一个rejected。
catch方法
在then的开头有提到过接收两个函数,第一个处理fullfilled状态,第二个处理rejected状态。而catch方法是一个语法糖,一个catch就相当于
Promise.prototype.then(null,onRejected),将then的方法改为下状态:
let i = 2;
let result = new Promise((resolve,reject) => {
if(i >1){
resolve(i)
} else {
reject(i)
}
})
result.then(
() => {console.log('resolve')})
.catch( consoole.log('rejected'))
console.log(result);
/*Promise {<fulfilled>: 2}
test.html:81 resolve*/
catch的优点在于当存在多个链式调用时,只需在最后写一个catch即可处理所有的错误情况。
new Promise( resolve => resolve(100))
.then(then1 => ok)
.then(then2 => console.log(then2))
.catch(error => console.log(error))
//ReferenceError: ok is not defined
//at test.html:86:20
finally方法
无论promise是rejected或者fullfilled 都会执行finally方法,且无论then和catch,是否执行 也会执行finally方法。虽然用的次数不如then和catch多,但在清除代码时用finally有奇效。
Promise的静态方法
promise并非绝对需要在待定状态下,通过执行器函数改变状态。
也可以通过Promise.resolve() 或者Promise.reject() 来改变状态。
Promise.resolve(1).then(then => {console.log(then)})
//1
除上述这些,promise还有一些别的用法,但主要和常用内容就这三点,其余可以参考MDN,或者JS红宝书。
异步函数
期约 Promise 是在ES6提出的,而异步函数则到了ES8。异步函数的底层依然是期约,相当于一个语法糖,但也有一些异同。
基本用法
async为带有异步特征的函数,async的结果是一个promise对象,若async有返回值(有return 语句),该值则为 promise resolve的值。
async function foo(){
return 1
}
foo().then( then => {console.log(then)})
//1
async函数本身是同步执行的,真正实现异步操作的是 async 内使用 await 关键字修饰的函数。即await 后面的内容都是异步执行。
function func(){
console.log(2345)
return 1
}
async function foo(){
let result = await func()
console.log(6789)
console.log(result)
return 10
}
console.log(foo())
/*注意输出的顺序
2345
Promise {<pending>} //实际上是resolve,但输出是promise为待定
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 10
6789
1 */
这里再定义了两个函数后,并没有执行语句,一直到最后一行 console.log(foo())
开始执行foo()函数。
foo函数第一行就是异步操作,后续所有内容都放到微任务中,此时宏任务中没有其他代码,开始执行 func()函数,控制台输出 2345 之后给result赋值为 1。
当 await函数执行完成之后 async 就已经生成一个 Promise 对象 但因为没有返回值 所以是 pending状态。
上图是在await所在行 插入断点后的输出状态。
随后执行后两行 输出 6789 以及result的值。并通过return 将 async的 promise对象 状态改为 fullfilled,值为 10。
异步函数的错误处理
引入async和await之后 函数本身就变为同步执行 因此 catch的方法就不再适用,否则会报错。而在遇到错误时,就要用 同步函数的错误处理模块 try()....catch()...
console.log(foo())
function func(){
console.log(ok) //并没有ok这个变量
return 1
}
async function foo(){
try {
let result = await func()
console.log(result)
console.log(5679)
} catch (error) {
console.log(error)
console.log(2345)
}
return 10
}
/*ReferenceError: ok is not defined
at func (test.html:87:21)
at foo (test.html:92:32)
at test.html:85:17
2345
Promise {<fulfilled>: 10}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 10*/
可见,当await 所修饰的函数出先错误时,将直接执行 catch 中的内容,不再执行func() 中的内容。
上述代码也可以通过 throw 的方法 改为 rejected
console.log(foo())
function func(){
console.log(ok)
return 1
}
async function foo(){
try {
let result = await func()
console.log(6789)
console.log(result)
} catch (error) {
throw error
console.log(2345)
}
return 10
}
foo().catch(error => {console.log(error)})
/*
Promise {<rejected>: ReferenceError: ok is not defined
at func
[[Prototype]]: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: ReferenceError: ok is not defined
ReferenceError: ok is not defined
at func (test.html:87:21)
at foo (test.html:92:32)
at test.html:103:5
Uncaught (in promise) ReferenceError: ok is not defined
at func (test.html:87:21)
at foo (test.html:92:32)
at test.html:102:17
此时async的状态为 rejected 。
同Promise一样,异步函数百分之七十以上的功能已经讲完,剩下的以后可以慢慢探索。
——完