其实有的地方还没有整理到,但是先整理到这啦,后面还有去看看别的题呢嘿嘿
目录
1 异步的产生
当代码要访问一些高延迟的资源,比如向远程服务器发送请求并等待响应,这个时候就需要长时间的等待。因为强制进程等待一个长时间的操作通常是不可行的,但是同步操作就必须等待,因此就需要异步操作和同步操作的配合。所以异步行为就是为了优化因计算量大而时间长的操作。
2 传统的异步模式
2.1 事件处理
某个对象的属性是一个函数,当发生某一件事时,运行该函数。
dom.onclick = function(){
}
2.2 回调函数
运行某个函数以实现某个功能的时候,传入一个函数作为参数,当发生某件事的时候,会运行该函数。
dom.addEventListener("click", function(){
})
3 传统异步模式的问题
3.1 回调地狱
所谓“回调地狱”,其实也就是嵌套回调。即异步返回的值又依赖另一个异步的返回值,那么回调就会变得更加复杂,外观上也更加繁琐,代码很难维护。
<div>
<button id="btn1">给按钮2注册点击事件</button>
<button id="btn2">给按钮3注册点击事件</button>
<button id="btn3">点击后弹出"hello"</button>
</div>
<script>
let btn1 = document.getElementById('btn1'),
btn2 = document.getElementById('btn2'),
btn3 = document.getElementById('btn3');
btn1.addEventListener('click',function(){
btn2.addEventListener('click',function(){
btn3.addEventListener('click',function(){
alert('hello')
})
})
})
</script>
比如上面这个代码,点击按钮3就需要依赖按钮2,按钮2就需要依赖按钮1,它们之间就形成了嵌套,如果三个按钮变成了一百个甚至更多个,代码将会变得更加地丑陋,而且很难维护,层层嵌套,形成了“回调地域”。
3.2 异步之间的联系
某个异步操作要等待多个异步操作的结果,对这种联系的处理,会让代码的复杂度剧增
4 Promise
4.1 Promise的状态
Promise可能处于3种状态:pending(待定),fulfilled(兑现),rejected(拒绝)。pending(待定)是Promise的最初状态,它可以settled(落定)为代表成功的fulfilled状态,也可以settled为代表失败的rejected状态。Promise的状态从pending转换为fullfilled或者rejected状态之后,状态就不可以再改变了。
4.2 Promise的用途
(1)可以通过Promise状态的转换来判断异步代码是否完成。
(2)在一些情况下,Promise封装的异步操作会实际生成某个值,程序希望可以获取到这个值。
为了实现这两种用途,在状态进行转换的时候,如果是fulfilled,那么会有一个值(value);如果是rejected,那么会有一个理由(reason)。可以进行使用。
4.3 如何控制Promise的状态(函数参数,静态方法)
可以通过调用Promise的两个函数参数来控制状态的转换,也可以通过调用Promise.resolve()静态方法。
(1)通过执行函数控制Promise状态
let p1 = new Promise((resolve,reject) => resolve())
console.log(p1)
上面的代码就会把Promise转换为fulfilled状态,调用reject()就会转换为rejected状态。
(2)通过调用Promise.resolve()和Promise.reject()静态方法
let p2 = Promise.resolve()
console.log(p2)
上面这段代码同样的把Promise转换为fulfilled状态,它们的输出结果是相同的。因此实际上是一样的。
说一下Promise.solve()和Promise.reject()不同的地方
首先,这两个静态方法都可以使Promise转换状态,但是不同的是,当传入的参数也是一个Promise时,Promise.resolve()的行为类似于一个空包装。但是Promise.reject()传入Promise参数时,这个传入的Promise会成为塔返回的拒绝Promise的理由。为了方便理解,代码如下:
let p = Promise.resolve(5)
console.log(Promise.resolve(p))
console.log(Promise.reject(p))
4.4 Promise的实例方法
在异步代码执行完了之后,对于fulfilled和rejected两种结果分别返回的value和reason,可以通过Promise的实例方法来进行处理,因此,Promise的实例方法是连接外部同步代码和内部异步代码之间的桥梁。它们可以访问异步操作返回的数据并进行处理。
(1)Promise.prototype.then()
这个方法是为Promise实例添加处理程序的主要方法。而且这个方法返回一个新的Promise实例。最多可以接收两个参数:onResolved处理程序和onRejected处理程序。这两个参数都是可选的,可以提供也可以不提供,要是提供的话,onResolved在Promise进入fulfilled状态时执行,onRejected在Promise进入rejected状态时执行。要是不提供这两个参数的话,Promise.resolve()就会包装上一个Promise解决之后的值。
首先,代码看看这个方法的实现原理:
function onResolved(id){
setTimeout(console.log,0,id,'resolved')
}
function onRejected(id){
setTimeout(console.log,0,id,'rejected')
}
let p1 = new Promise((resolve,reject) => resolve())
let p2 = new Promise((resolve,reject) => reject())
p1.then(() => onResolved('p1'),
() => onRejected('p1'))
p2.then(() => onResolved('p2'),
() => onRejected('p2'))
上面的程序也可以只提供onRejected参数,需要在onResolved的位置上传入undefined
使用这个Promise.then()方法的几种情况:
a.调用then()时不传处理程序,原样向后传。像下面这样,p2调用then()的时候什么都没有传,那么p2的状态还有value都和p1是一样的。
let p1 = Promise.resolve('test')
let p2 = p1.then()
console.log(p2)
b.调用then()的时候传了参数,但是没有显式的返回语句,像下面这种,那么就会返回undefined,状态仍然是fulfilled。
let p1 = Promise.resolve('test')
let p2 = p1.then(() => undefined)
let p3 = p1.then(() => {})
let p4 = p1.then(() => Promise.resolve());
console.log(p2,p3,p4)
c.如果调用的时候有显式的返回值,那么就会返回这个值。这种情况和前面的是一样的,只不过返回的值就是调用then()的时候传入的值。(传入的是Promise.reject()的话状态也会变到rejected)就不写代码啦。
要是抛出错误(p.then(() => {throw 'test'})的话,返回的就是rejected状态的promise,要是返回错误值(p.then(() => Error('test'))的话,返回的仍然是fulfilled状态的promise。
调用onRejected处理程序的时候和上面的情况时类似的,onRejected处理程序返回的值也被Promise.resolve()包装。因为其实在进入rejected状态之后,代表捕获了错误,因此相当于是符合预期的行为,返回一个fulfilled的Promise。
(2)Promise.prototype.catch()
其实这个方法吧,相当于是promise.then()方法第一个参数为null,传入第二个参数(调用onRejected处理程序)。返回的也是一个新的Promise。
(3)Promise.prototype.finally()
作用是避免onResolved和onRejected处理程序中出现冗余代码。返回的也是一个新的Promise。
这个方法与状态无关。不管是fulfilled还是rejected状态,大多都表现为父Promise状态的传递。如果传入的是pending状态或者rejected状态的Promise,会返回相应的Promise。
4.5 两个合成Promise的静态方法
Promise.all()和Promise.race()是Promise类提供的将多个Promise实例组合成一个Promise的静态方法。
Promise.all()是一组Promise全部都转换到fulfilled状态之后才会转换到fulfilled状态;
(1)要是一组里面都是解决的Promise,那么返回的就是所有包含value的数组。
let p = Promise.all([Promise.resolve(1),Promise.resolve(2),Promise.resolve(3)])
console.log(p)
(2)要是一组里面出现了一个拒绝的Promise,那么返回的Promise就进入rejected状态,reason就是拒绝的那个Promise的reason
let p = Promise.all([Promise.resolve(1),Promise.resolve(2),Promise.reject(3)])
console.log(p)
Promise.race()就类似于比赛,一组Promise中只要有一个Promise转换到fulfilled或者rejected状态,那么就结束了。Promise.race()只要第一个解决或者拒绝的Promise出现就可以,新的Promise的状态以及value/reason就是第一个落定的这个Promise的。代码就不写啦,和前面的一个道理。
!!注意:Promise.all()和Promise.race()在使用的时候,语法都要像下面这样:
let p1 = Promise.all([Promise.resolve(),Promise.resolve(),Promise.reject()])
let p1 = Promise.race([Promise.resolve(),Promise.reject()])
要是空的可迭代对象,要这样写:
let p1 = Promise.race([])
要是这样,语法就是无效的。
let p1 = Promise.race()
即调用这两个方法的时候,要把传入的Promise放到一个[]里面。
5 异步函数(async/await)
异步函数(async/await)是ES8规范新增的,是ES6的Promise在函数中的应用。
async/await让以同步方式写的代码可以异步执行。
1.async
async关键字用于声明异步函数。可以用在函数声明、函数表达式、箭头函数和方法上:
async function test(){}//函数声明
let test = async function() {}//函数表达式
let test = async () => {}//箭头函数
class Test(){
async test(){}//方法
}
像上面这样,在使用async声明之后,函数就具有了异步的特征。对于参数、闭包,异步函数仍然具有普通js函数的正常行为, 像下面这个代码,函数虽然有了异步的特征,但是代码还是同步求值的:
async function test(){
console.log(1)
}
test();
console.log(2)
因此上面的输出结果还是按正常的同步代码的顺序的。
但是,在被async声明为异步函数之后,如果函数内部使用了return关键字返回了值,(没有return会返回undefined),这个值会被Promise.resolve()包装成一个Promise对象。在函数外部调用可以得到它返回的Promise:
async function test(){
console.log(1)
return 3;//这句代码等同于:return Promise.resolve(3),返回的结果都是一样的
}
test().then(value => console.log(value));
console.log(2)
同样的,如果把return一个值换成抛出一个错误(throw 3),那么返回的就是一个拒绝的promise。
async function test(){
console.log(1)
throw 3
}
test().catch(reason => console.log(reason))
console.log(2)
这种情况下抛出的错误会被异步函数捕获。但是,拒绝Promise的错误不会被异步函数捕获,像下面这样:
async function test(){
console.log(1)
Promise.reject(3)
}
test().catch(reason => console.log(reason))
console.log(2)
2.await
await关键字可以暂停异步函数代码的执行,等待Promise解决。 await必须在异步函数中使用,即有await就有async。然后Promise解决完之后把值传给表达式,再回复异步函数的执行。可以单独使用,也可以在表达式中使用:
async function test(){
console.log(await Promise.resolve(1))
}
test()
async function demo(){
return await Promise.resolve(1)
}
demo().then(console.log)
同样的,如果await等待的是一个会抛出错误的同步操作,会返回拒绝的promise。
async function test(){
console.log(1)
await (() => {throw 3;})()
}
test().catch(console.log)
console.log(2)
在前面的async部分一样,拒绝的promise不会被异步函数捕获,会抛出错误。对拒绝的promise使用await就可以释放错误值。
async function test(){
await Promise.reject(2)
}
test().catch(console.log)
async/await中真正起作用的是await。异步函数如果不包含await关键字,其执行基本上跟普通函数没什么区别。js运行时,碰到await时,会记录在哪里暂停执行,然后等待await后面的值可用,再继续恢复异步函数的执行。
await这行代码后面的部分会被异步求值:
async function test(){
console.log(1)
console.log(await Promise.resolve(2));
console.log(3)
}
test()
console.log(4)