事实上async function只不过是对Promise一个很好的封装,从es6到es7,而async异步方法确实实现起来 也可让代码变得很优雅,下面就由浅到深具体说说其中的原理。
html
promise是es6中实现的一个对象,它接收一个函数做为参数。这个函数又有两个参数,分别是 resolve和reject。node
const a = new Promise(function(resolve, reject){
console.log(1)
resolve(3)
reject(5)
console.log(4)
})
console.log('outter')
console.log(a)
复制代码
结果以下:git
console.log(4)
在最后一行,可是依然比resolve先执行。尽管如此,写代码时仍是应该要注意。(再看看上面,出口天然应该放在逻辑的最后一步)。
const a = new Promise(function(resolve, reject){
console.log(1)
setTimeout( () => {
resolve('inner')
})
})
console.log('outter')
console.log(a)
复制代码
const a = new Promise(function(resolve, reject){
console.log(1) //1
resolve('inner')
})
console.log('outter') //2
a.then(v => {
console.log(v) //4
})
console.log(a) //3
复制代码
const a = new Promise(function(resolve, reject){
console.log(1)
resolve('inner')
})
const b = a.then(v => {
console.log(v)
})
setTimeout( () => {
console.log(b)
})
复制代码
首先说明一下,全部的setTimeout(包括setInterval)都默认至少有一个4ms,就算你不写。而且setTimeout是浏览器提供的另外一个线程来实现,而promise则是做为es6的规范。(若是用node就好解释了,我更倾向于认为Promise是相似于nexttick之类的接口。浏览器环境下的js并不像node具备多个队列,只有一个主线程运行队列,Promise必定会在当前主线程队列运行完毕的最后一个)。不了解的node也无所谓,这里只须要记住promise必定比setTimeout快!setTimeout有4ms呢!言归正传,实质上这里是帮咱们返回了一个已是resolved状态的Promise(具体规则见mdn),而且由于咱们并无传递参数,所以这里接收到的参数就是undefined。接着看代码es6
const a = new Promise(function(resolve, reject){
resolve('inner')
})
const b = a.then(v => {
return new Promise((resolve, reject) => {
resolve(v)
})
})
setTimeout( () => {
console.log(b)
},1000)
复制代码
a.then(v => {
return new Promise((resolve, reject) => {
resolve(v)
})
}).then(v => {
}).then(v =>{
})
复制代码
哎哟,then一多,好丑啊。代码一点也不优雅,是的。这确实是个问题,这才引出了async的解决方案,但还不到谈那个的时间。让咱们先把promise说完。
可能有小伙伴发现了,reject你一直都没说呢?是的,先说完resolve再说这个,其实我我的理解rejcet为抛出一个异常,咱们能够在then中去处理,可是咱们也能够在catch中处理(我推荐这种,至于为何,我把两种写法列出来你就明白了)。
如今假设有一个业务逻辑,须要判断以后咱们再决定走哪一个出口。下面第一种是用then的github
const a = new Promise(function(resolve, reject){
if(0) {
resolve('成功了')
} else {
reject('错误了')
}
})
a.then( v => {
console.log(v)
return new Promise((resolve, reject) => {
resolve(v)
})
}, e => {
console.log(e)
})
复制代码
a.then( v => {
console.log(v)
return new Promise((resolve, reject) => {
resolve(v)
})
}).catch(e => {
console.log(e)
})
复制代码
结果图如上,我就不贴了。是否是很优雅?(额。。。。单纯指的是相对then来讲。)
总而言之,resolve,reject。对应两种状态,两种出口,出口中传递参数。出口以前都为同步。出口以后,then,catch都是异步,而且咱们能够在then和catch中接收以前同步的传出来的参数。而且要注意的是resolve状态和reject两种出口咱们要用不一样的方式来接收。一种我认为是成功,一种是异常,异常必需要去捕获。
说到这里其实promise也差很少了,再提两个方法,一个是Promise.race,一个是Promise.all。注意了,这两个都是类方法。Promise.race方法是将多个 Promise 实例,包装成一个新的 Promise 实例。数组
const result = Promise.race([a, b, c]);
复制代码
a,b,c都是promise的实例,这三个实例哪个先结束,就先返回一个。result就变成哪个。举个场景就明白了。如今咱们须要一张图片,这个图片异步加载,可是它是哪张我不关心(只要是给定的三张中的一张),我定了三个异步任务,先返回的那张我放到html上。嗯,就这么简单。可是要注意,若是第一个结束的是错误的,同样也是算做跑最快的那个,返回给result。所以外面应该用catch接收一下,同时自行判断逻辑(可能由于网络的缘由须要咱们再执行一遍啦仍是啥)
Promise.all。他必需要接收的promise实例所有变为resolve才返回(返回这些promise实例中resolve中的参数组成的数组),有一个变成reject,它就返回这个reject的参数。直接举例子。咱们须要异步加载三张图片,可是我必需要三张所有加载完我一块儿显示,我不要一张一张的出来。三张都出来就是resolve,任意一张失败了很差意思我就都不给你显示。
剩下的还有一些promise方法我就很少说了,用的也很少,直接看文档就行了。promise
实际上,async function的使用方法跟普通函数如出一辙,若是你在async function中没有使用await关键字 的话,从某种程度上来讲它就是普通函数。。。。先来个代码压压惊。浏览器
async function test() {
console.log(1)
const a = await new Promise(function(resolve, reject){
resolve(3)
})
console.log(a)
}
test()
console.log(2)
复制代码
仍是同样,代码说话bash
async function test() {
console.log(1)
const a = await new Promise(function(resolve, reject){
resolve(3)
})
console.log('我是被处理后的:', a)
}
const b = test()
console.log('我是还没被处理后的:', b)
复制代码
resolve(3)
,而后咱们在then中接收这个参数,可是如今await直接就帮咱们处理了这个参数,也就是await会处理这个promise对象,再返回里面的参数。而处理以前,咱们主线程任务必须先运行完。所以这里咱们打印的b的结果正是一个处于pending状态的promise对象。其次,async function只要一到await,那么函数这时候就等同于异步了, (见上面代码,再看看这段话的开头)。总而言之就是当咱们运行到await的时候,在这个async function内部在await以后的全部代码都会 等待await处理完毕结果以后才会执行,而当await开始处理结果,很差意思,这就不属于同步的范围了。(
是否是异步极其优雅的实现方法!!!)
有的同窗就问了,那么await只能处理promise对象吗,不是的。见代码网络
async function test() {
console.log(1)
const b = await '我经常由于本身不够优秀而感到恐慌'
console.log(b)
const a = await new Promise(function(resolve, reject){
resolve(3)
})
console.log('我是被处理后的:', a)
}
const b = test()
console.log('我是还没被处理后的:', b)
复制代码
async function test() {
const a = await new Promise(function(resolve, reject){
reject(3)
})
}
test()
复制代码
报错了。。咋办呢。。。
async function test() {
try {
const a = await new Promise(function(resolve, reject){
reject('完蛋,我会被捕获')
})
} catch(e) {
console.log(e)
}
}
test()
复制代码
很好,下面来个再度进阶的,也是我我的以前遇到的一个坑。场景是这样的,我作了一个很是简单的爬虫(puppeteer),主要就是爬取图片而后下载到本地。 场景中有这样一段代码。
srcs.forEach( async(src) => {
await srcImages(srcs[i], config.cat)
console.log(1)
page.waitfor(200)
})
async srcImages(){conole.log(2)//balabalabalabala具体逻辑就略过了}
复制代码
大概意思就是我爬取了每一个图片的网址,放在一个数组里,而后对数组里面每一个地址都调用一个函数,这个函数负责下载。而且这个函数就是用async包裹的,(还记得async就是把一整个函数用Promise包裹吗),而后每一次下载完我都等待200ms,避免操做太频繁把我IP封了。问题来了,小伙伴猜猜输出????
结果证实,我一开始的想法彻底错误,这些1是连续打出来的。嘿嘿嘿,为何会这样呢?我来捋一捋,咱们一开始对数组第一项进行操做,遇到了第一个await,很好,后面代码所有异步等待。关键来了,主线任务接着运行,开始操做数组的第二项。。。。。就这样,把数组所有遍历完毕以后,咱们再所有一块儿下载(以前都所有挂在异步等待同步的主线程运行结束呢)。。。。。所有下载完后,咱们打印全部的2。。。。而后咱们再咱们打印全部的1。。。咱们再等待200ms * 数组长度的时间。。 。饿。。。坑人。。。那么我最后是怎么解决的?
async function test() {
for(let i = 0; i < srcs.length; i++) {
await srcImages(srcs[i], config.cat)
await page.waitfor(200)
}
}
test()
复制代码
嘿嘿嘿,仍是用for循环来代替forEach好一些。这里也给我提了个警钟,当传统的那些forEach,map之流遇到async的时候,仍是应该注意一下的,可能会跟预想的逻辑不同哦。
在这篇文章以后,后续的文章我应该都只会发到本身的博客上。每次发的时候应该都会在掘金沸点更新一下。但若是文章较长的话我应该也会先在掘金上更新一下(毕竟图片多了的话。掘金上外链直接生成,而不是存在本地)。
好了,到这里也就结束了。不了解node的小伙伴们能够撤了。下面贴一个在node中本身实现的promisify方法。
const fs = require('fs');
function promisify(f) {
return function() { //虽然这里函数没参数,但运行时确定会有参数哦
let args = Array.prototype.slice.call(arguments)
return new Promise((resolve,reject) => {
args.push((err, result) => {
if(err) {
reject(err)
} else {
resolve(result)
}
})
f.apply(null, args)
})
}
}
readFile = promisify(fs.readFile);
//基础版
readFile('./app.js').then( data => {
console.log(data.toString())
}).catch(e => {
console.log(e)
})
//进阶版! 使用async await
async funtion test() {
try {
const content = await readFile('./app.js')
console.log(content)
} catch(e) {
}
}
复制代码
回调地狱问题在node中很是明显,而咱们经过promisify能够将一个函数转化为Promise对象。node中任何一个函数的最后一个回调函数必定是(err, data) => {}
。所以这里咱们就把其做为数组的最后一项。若是err咱们就从reject出口 出去,若是成功就从resolve出口出去。而第一步promisify则是有点像是函数柯里化,返回一个函数地址。好了文章到这里就结束了。