promise async await使用
1.Promise
(名字含义:promise为承诺,表示其他手段无法改变)
- Pending(进行中、未完成的)
- Resolved(已完成,又称 Fulfilled)
- Rejected(已失败)
promises的优势
1.解决回调地狱
2.更好地进行错误捕获
有时我们要进行一些相互间有依赖关系的异步操作,比如有多个请求,后一个的请求需要上一次请求的返回结果。过去常规做法只能 callback 层层嵌套,但嵌套层数过多的话就会有 callback hell 问题。比如下面代码,可读性和维护性都很差的。
firstAsync(function(data){ //处理得到的 data 数据 //.... secondAsync(function(data2){ //处理得到的 data2 数据 //.... thirdAsync(function(data3){ //处理得到的 data3 数据 //.... }); }); });
promise例子1
//创建一个Promise实例,获取数据。并把数据传递给处理函数resolve和reject。需要注意的是Promise在声明的时候就执行了。 var getUserInfo=new Promise(function(resolve,reject){ $.ajax({ type:"get", url:"index.aspx", success:function(){ if(res.code=="ok"){ resolve(res.msg)//在异步操作成功时调用 }else{ reject(res.msg);//在异步操作失败时调用 } } }); }) //另一个ajax Promise对象, var getDataList=new Promise(function(resolve,reject){ $.ajax({ type:"get", url:"index.aspx", success:function(res){ if(res.code=="ok"){ resolve(res.msg)//在异步操作成功时调用 }else{ reject(res.msg);//在异步操作失败时调用 } } }); }) //Promise的方法then,catch方法 getUserInfo.then(function(ResultJson){ //通过拿到的数据渲染页面 }).catch(function(ErrMsg){ //获取数据失败时的处理逻辑 }) //Promise的all方法,等数组中的所有promise对象都完成执行 Promise.all([getUserInfo,getDataList]).then(function([ResultJson1,ResultJson2]){ //这里写等这两个ajax都成功返回数据才执行的业务逻辑 })
注意:成功的结果需要用resolve包裹,失败的结果需要用reject包裹
promise例子2
ajax请求
var getData=new Promise(function(resolve,reject){ $.post("http://apptest.hcbkeji.com/php/option/activity/chevron_report_activity.php", {flag: 'click',act:'临沂页面',page:'临沂上报活动'}, function (res) { resolve(res) } ); }) getData.then(res=>{ console.log(res) //{"type":"ok"} })
promise例子3
var test=new Promise((resolve,reject)=>{ setTimeout(function(){ resolve('hello world') },2000) }) test.then(res=>{ console.log(res) }) console.log('虽然在后面,但是我先执行') //打印结果 // 虽然在后面,但是我先执行 // hello world
promise例子4
function mytest(){ return new Promise((resolve,reject)=>{ $.post("http://apptest.hcbkeji.com/php/option/activity/chevron_report_activity.php", {flag: 'click',act:'临沂页面',page:'临沂上报活动'}, function (res) { var res=JSON.parse(res) resolve(res) } ); }) } mytest().then(res=>{ console.log(res) }) console.log('虽然在后面,但是我先执行')
2.async
async使用
作为一个关键字放到函数前面,用于表示函数是一个异步函数,因为async就是异步的意思, 异步函数也就意味着该函数的执行不会阻塞后面代码的执行
调用方法:async 函数也是函数,平时我们怎么使用函数就怎么使用它,直接加方法名括号调用。
async function test(){ return 'hello world' } test().then(res=>{ console.log(res) }) console.log('虽然在后面,但是我先执行') //打印结果 //虽然在后面,但是我先执行 //hello world
首先打印 ‘虽然在后面,但是我先执行’ ,后执行 打印 ‘hello world’
async的作用:输出的是一个 Promise 对象
async function testAsync() { return "hello async"; } let result = testAsync(); console.log(result)
打印结果Promise {<resolved>: "hello async"} 从结果中可以看到async函数返回的是一个promise对象,如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve()
封装成 Promise 对象。
async function testAsync1() { console.log("hello async"); } let result1 = testAsync1(); console.log(result1);
结果返回Promise.resolve(undefined) 因为使用async 但是函数没有return一个直接量
async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中 return
一个直接量,async 会把这个直接量通过 Promise.resolve()
封装成 Promise 对象。
async 函数返回的是一个 Promise 对象,所以在最外层不能用 await 获取其返回值的情况下,我们当然应该用原来的方式:then()
链来处理这个 Promise 对象。
Promise 的特点是无等待,所以在没有 await
的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。
Promise 有一个resolved,这是async 函数内部的实现原理。如果async 函数中有返回一个值 ,当调用该函数时,内部会调用Promise.solve() 方法把它转化成一个promise 对象作为返回,但如果timeout 函数内部抛出错误呢? 那么就会调用Promise.reject() 返回一个promise 对象, 这时修改一下timeout 函数
async function timeout(flag) { if (flag) { return 'hello world' } else { throw 'my god, failure' } } console.log(timeout(true)) // 调用Promise.resolve() 返回promise 对象。 console.log(timeout(false)); // 调用Promise.reject() 返回promise 对象。
控制台如下:
如果函数内部抛出错误, promise 对象有一个catch 方法进行捕获。
timeout(false).catch(err => { console.log(err) })
3.await
因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数,但要清楚,它等的实际是一个返回值。注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者promise对象
注意:await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。
function getSomething() { return "something"; } async function testAsync() { return Promise.resolve("hello async"); } async function test() { const v1 = await getSomething(); //await后接普通函数调用 const v2 = await testAsync(); //await后接async promise对象 console.log(v1, v2); } test(); //打印结果something hello async
await
是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西。
如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。
综合:上面已经说明了 async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。
使用promise和async await比较
使用promise
function takeLongTime(n){ return new Promise(resolve=>{ setTimeout(()=>resolve(n+200),n) }) } function step1(n){ console.log(`step1 with ${n}`) return takeLongTime(n); } function step2(n){ console.log(`step2 with ${n}`) return takeLongTime(n); } function step3(n){ console.log(`step3 with ${n}`) return takeLongTime(n); } function run(){ console.time('run') const time1=300; step1(time1) .then(time2=>step2(time2)) .then(time3=>step3(time3)) .then(result=>{ console.log(`resutlt is ${result}`) console.timeEnd('run') }) } run()
打印结果
step1 with 300 step2 with 500 step3 with 700 run: 1504.652099609375ms
使用async await
function takeLongTime(n){ return new Promise(resolve=>{ setTimeout(()=>resolve(n+200),n) }) } function step1(n){ console.log(`step1 with ${n}`) return takeLongTime(n); } function step2(n){ console.log(`step2 with ${n}`) return takeLongTime(n); } function step3(n){ console.log(`step3 with ${n}`) return takeLongTime(n); } async function run(){ console.time('run') const time1=300 const time2=await step1(time1) const time3=await step1(time2) const result=await step1(time3) console.log(`result is ${result}`) console.timeEnd('run') } run()
结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样
await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中
async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); } } // 另一种写法 async function myFunction() { await somethingThatReturnsAPromise().catch(function (err){ console.log(err); }); }
async function dbFuc(db) { let docs = [{}, {}, {}]; // 报错 docs.forEach(function (doc) { await db.post(doc); }); }
await 关键字,await是等待的意思,那么它等待什么呢,它后面跟着什么呢?其实它后面可以放任何表达式,不过我们更多的是放一个返回promise 对象的表达式。注意await 关键字只能放到async 函数里面
现在写一个函数,让它返回promise 对象,该函数的作用是2s 之后让数值乘以2
// 2s 之后返回双倍的值 function doubleAfter2seconds(num) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(2 * num) }, 2000); } ) }
现在再写一个async 函数,从而可以使用await 关键字, await 后面放置的就是返回promise对象的一个表达式,所以它后面可以写上 doubleAfter2seconds 函数的调用
async function testResult() { let result = await doubleAfter2seconds(30); console.log(result); }
现在调用testResult 函数
testResult();
打开控制台,2s 之后,输出了60.
现在我们看看代码的执行过程,调用testResult 函数,它里面遇到了await, await 表示等一下,代码就暂停到这里,不再向下执行了,它等什么呢?等后面的promise对象执行完毕,然后拿到promise resolve 的值并进行返回,返回值拿到之后,它继续向下执行。具体到 我们的代码, 遇到await 之后,代码就暂停执行了, 等待doubleAfter2seconds(30) 执行完毕,doubleAfter2seconds(30) 返回的promise 开始执行,2秒 之后,promise resolve 了, 并返回了值为60, 这时await 才拿到返回值60, 然后赋值给result, 暂停结束,代码才开始继续执行,执行 console.log语句。
就这一个函数,我们可能看不出async/await 的作用,如果我们要计算3个数的值,然后把得到的值进行输出呢?
async function testResult() { let first = await doubleAfter2seconds(30); let second = await doubleAfter2seconds(50); let third = await doubleAfter2seconds(30); console.log(first + second + third); }
6秒后,控制台输出220, 我们可以看到,写异步代码就像写同步代码一样了,再也没有回调地域了。
补充:在vue中使用
设计使用场景,一个手机充值活动,实现选择用户的省市,点击充值按钮,弹出相应省份的充值列表,2个请求。
请求1 获取所在省市: 根据手机号得到省和市 方法命名为getLocation 接受一个参数phoneNum 返回结果 province 和city
请求2 获取可充值面值列表:根据省和市得到充值面值列表 方法命名为getFaceList 接受两个参数province 和city 返回充值列表
我们首先要根据手机号得到省和市,所以写一个方法来发送请求获取省和市,方法命名为getLocation, 接受一个参数phoneNum ,当获取到城市位置以后,我们再发送请求获取充值面值,所以还要再写一个方法getFaceList, 它接受两个参数, province 和city,
methods: { //获取到城市信息 getLocation(phoneNum) { return axios.post('phoneLocation', {phoneNum:phoneNum}) }, // 获取面值 getFaceList(province, city) { return axios.post('/faceList', {province:province,city:city}) }, // 点击确定按钮时,显示面值列表 getFaceResult () { this.getLocation(this.phoneNum) .then(res => { if (res.code=='ok') { let province = res.data.obj.province; let city = res.data.obj.city; this.getFaceList(province, city) .then(res => { if(res.code=='ok') { //最终获取到面值列表 this.faceList = res.data.obj } }) } }) .catch(err => { console.log(err) }) } }
现在点击确定按钮,可以看到页面中输出了 从后台返回的面值列表。这时你看到了then 的链式写法,有一点回调地域的感觉。现在我们在有async/ await 来改造一下。
首先把 getFaceResult 转化成一个async 函数,就是在其前面加async, 因为它的调用方法和普通函数的调用方法是一致,所以没有什么问题。然后就把 getLocation 和
methods: { //获取到城市信息 getLocation(phoneNum) { return axios.post('phoneLocation', {phoneNum:phoneNum}) }, // 获取面值 getFaceList(province, city) { return axios.post('/faceList', {province:province,city:city}) }, // 点击确定按钮时,显示面值列表 async getFaceResult () { let location = await this.getLocation(this.phoneNum); if (location.code=='ok') { let province = location.data.obj.province; let city = location.data.obj.city; let result = await this.getFaceList(province, city); if (result.code=='ok') { this.faceList = result.data.obj; } } } }
现在代码的书写方式,就像写同步代码一样,没有回调的感觉,非常舒服。
现在就还差一点需要说明,那就是怎么处理异常,如果请求发生异常,怎么处理? 它用的是try/catch 来捕获异常,把await 放到 try 中进行执行,如有异常,就使用catch 进行处理。
methods: { //获取到城市信息 getLocation(phoneNum) { return axios.post('phoneLocation', {phoneNum:phoneNum}) }, // 获取面值 getFaceList(province, city) { return axios.post('/faceList', {province:province,city:city}) }, // 点击确定按钮时,显示面值列表 async getFaceResult () { try { let location = await this.getLocation(this.phoneNum); if (location.code=='ok') { let province = location.data.obj.province; let city = location.data.obj.city; let result = await this.getFaceList(province, city); if (result.code=='ok') { this.faceList = result.data.obj; } } } catch(err) { console.log(err); } } }
参考:https://www.cnblogs.com/SamWeb/p/8417940.html
异步解决方案 async和await
前言
异步编程模式在前端开发过程中,显得越来越重要。从最开始的XHR到封装后的Ajax都在试图解决异步编程过程中的问题。随着ES6新标准的到来,处理异步数据流又有了新的方案。我们都知道,在传统的ajax请求中,当异步请求之间的数据存在依赖关系的时候,就可能产生很难看的多层回调,俗称'回调地狱'(callback hell),这却让人望而生畏,Promise的出现让我们告别回调函数,写出更优雅的异步代码。在实践过程中,却发现Promise并不完美,Async/Await是近年来JavaScript添加的最革命性的的特性之一,Async/Await提供了一种使得异步代码看起来像同步代码的替代方法。接下来我们介绍这两种处理异步编程的方案。
一、Promise的原理与基本语法
1.Promise的原理
Promise 是一种对异步操作的封装,可以通过独立的接口添加在异步操作执行成功、失败时执行的方法。主流的规范是 Promises/A+。
Promise中有几个状态:
-
pending: 初始状态, 非 fulfilled 或 rejected;
-
fulfilled: 成功的操作,为表述方便,fulfilled 使用 resolved 代替;
-
rejected: 失败的操作。
pending可以转化为fulfilled或rejected并且只能转化一次,也就是说如果pending转化到fulfilled状态,那么就不能再转化到rejected。并且fulfilled和rejected状态只能由pending转化而来,两者之间不能互相转换。
2.Promise的基本语法
-
Promise实例必须实现then这个方法
-
then()必须可以接收两个函数作为参数
-
then()返回的必须是一个Promise实例
<script src="https://cdn.bootcss.com/bluebird/3.5.1/bluebird.min.js"></script>//如果低版本浏览器不支持Promise,通过cdn这种方式
<script type="text/javascript">
function loadImg(src) {
var promise = new Promise(function (resolve, reject) {
var img = document.createElement('img')
img.onload = function () {
resolve(img)
}
img.onerror = function () {
reject('图片加载失败')
}
img.src = src
})
return promise
}
var src = 'https://www.imooc.com/static/img/index/logo_new.png'
var result = loadImg(src)
result.then(function (img) {
console.log(1, img.width)
return img
}, function () {
console.log('error 1')
}).then(function (img) {
console.log(2, img.height)
})
</script>
二、Promise多个串联操作
Promise还可以做更多的事情,比如,有若干个异步任务,需要先做任务1,如果成功后再做任务2,任何任务失败则不再继续并执行错误处理函数。要串行执行这样的异步任务,不用Promise需要写一层一层的嵌套代码。
有了Promise,我们只需要简单地写job1.then(job2).then(job3).catch(handleError);
其中job1、job2和job3都是Promise对象。
比如我们想实现第一个图片加载完成后,再加载第二个图片,如果其中有一个执行失败,就执行错误函数:
var src1 = 'https://www.imooc.com/static/img/index/logo_new.png'
var result1 = loadImg(src1) //result1是Promise对象
var src2 = 'https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg'
var result2 = loadImg(src2) //result2是Promise对象
result1.then(function (img1) {
console.log('第一个图片加载完成', img1.width)
return result2 // 链式操作
}).then(function (img2) {
console.log('第二个图片加载完成', img2.width)
}).catch(function (ex) {
console.log(ex)
})
这里需注意的是:then 方法可以被同一个 promise 调用多次,then 方法必须返回一个 promise 对象。上例中result1.then如果没有明文返回Promise实例,就默认为本身Promise实例即result1,result1.then返回了result2实例,后面再执行.then实际上执行的是result2.then
三、Promise常用方法
除了串行执行若干异步任务外,Promise还可以并行执行异步任务。
试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all()实现如下:
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 获得一个Array: ['P1', 'P2']
});
有些时候,多个异步任务是为了容错。比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()实现:
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
console.log(result); // 'P1'
});
由于p1执行较快,Promise的then()将获得结果'P1'。p2仍在继续执行,但执行结果将被丢弃。
总结:Promise.all接受一个promise对象的数组,待全部完成之后,统一执行success;
Promise.race接受一个包含多个promise对象的数组,只要有一个完成,就执行success
接下来我们对上面的例子做下修改,加深对这两者的理解:
var src1 = 'https://www.imooc.com/static/img/index/logo_new.png'
var result1 = loadImg(src1)
var src2 = 'https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg'
var result2 = loadImg(src2)
Promise.all([result1, result2]).then(function (datas) {
console.log('all', datas[0])//<img src="https://www.imooc.com/static/img/index/logo_new.png">
console.log('all', datas[1])//<img src="https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg">
})
Promise.race([result1, result2]).then(function (data) {
console.log('race', data)//<img src="https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg">
})
如果我们组合使用Promise,就可以把很多异步任务以并行和串行的方式组合起来执行
四、Async/Await简介与用法
异步操作是 JavaScript 编程的麻烦事,很多人认为async函数是异步操作的终极解决方案。
1、Async/Await简介
-
async/await是写异步代码的新方式,优于回调函数和Promise。
-
async/await是基于Promise实现的,它不能用于普通的回调函数。
-
async/await与Promise一样,是非阻塞的。
-
async/await使得异步代码看起来像同步代码,再也没有回调函数。但是改变不了JS单线程、异步的本质。
2、Async/Await的用法
-
使用await,函数必须用async标识
-
await后面跟的是一个Promise实例
-
需要安装babel-polyfill,安装后记得引入 //npm i --save-dev babel-polyfill
function loadImg(src) {
const promise = new Promise(function (resolve, reject) {
const img = document.createElement('img')
img.onload = function () {
resolve(img)
}
img.onerror = function () {
reject('图片加载失败')
}
img.src = src
})
return promise
}
const src1 = 'https://www.imooc.com/static/img/index/logo_new.png'
const src2 = 'https://img1.mukewang.com/545862fe00017c2602200220-100-100.jpg'
const load = async function(){
const result1 = await loadImg(src1)
console.log(result1)
const result2 = await loadImg(src2)
console.log(result2)
}
load()
当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。
五、Async/Await错误处理
await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中。try..catch错误处理也比较符合我们平常编写同步代码时候处理的逻辑。
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
六、为什么Async/Await更好?
Async/Await较Promise有诸多好处,以下介绍其中三种优势:
1. 简洁
使用Async/Await明显节约了不少代码。我们不需要写.then,不需要写匿名函数处理Promise的resolve值,也不需要定义多余的data变量,还避免了嵌套代码。
2. 中间值
你很可能遇到过这样的场景,调用promise1,使用promise1返回的结果去调用promise2,然后使用两者的结果去调用promise3。你的代码很可能是这样的:
const makeRequest = () => {
return promise1()
.then(value1 => {
return promise2(value1)
.then(value2 => {
return promise3(value1, value2)
})
})
}
使用async/await的话,代码会变得异常简单和直观
const makeRequest = async () => {
const value1 = await promise1()
const value2 = await promise2(value1)
return promise3(value1, value2)
}
3.条件语句
下面示例中,需要获取数据,然后根据返回数据决定是直接返回,还是继续获取更多的数据。
const makeRequest = () => {
return getJSON()
.then(data => {
if (data.needsAnotherRequest) {
return makeAnotherRequest(data)
.then(moreData => {
console.log(moreData)
return moreData
})
} else {
console.log(data)
return data
}
})
}
代码嵌套(6层)可读性较差,它们传达的意思只是需要将最终结果传递到最外层的Promise。使用async/await编写可以大大地提高可读性:
const makeRequest = async () => {
const data = await getJSON()
if (data.needsAnotherRequest) {
const moreData = await makeAnotherRequest(data);
console.log(moreData)
return moreData
} else {
console.log(data)
return data
}
}
参考文章
ES6 Promise 并行执行和顺序执行
1.Promise.all 并行执行promise
getA和getB并行执行,然后输出结果。如果有一个错误,就抛出错误
/**
* 每一个promise都必须返回resolve结果才正确
* 每一个promise都不处理错误
*/
const getA = new Promise((resolve, reject) => {
//模拟异步任务
setTimeout(function(){
resolve(2);
}, 1000)
})
.then(result => result)
const getB = new Promise((resolve, reject) => {
setTimeout(function(){
// resolve(3);
reject('Error in getB');
}, 1000)
})
.then(result => result)
Promise.all([getA, getB]).then(data=>{
console.log(data)
})
.catch(e => console.log(e));
getA和getB并行执行,然后输出结果。总是返回resolve结果
/**
* 每一个promise自己处理错误
*/
const getA = new Promise((resolve, reject) => {
//模拟异步任务
setTimeout(function(){
resolve(2);
}, 1000)
})
.then(result => result)
.catch(e=>{
})
const getB = new Promise((resolve, reject) => {
setTimeout(function(){
// resolve(3);
reject('Error in getB');
}, 1000)
})
.then(result => result)
.catch(e=>e)
Promise.all([getA, getB]).then(data=>{
console.log(data)
})
.catch(e => console.log(e));
2.顺序执行promise
先getA然后getB执行,最后addAB
- 2.1 方法一——连续使用then链式操作
function getA(){
return new Promise(function(resolve, reject){
setTimeout(function(){
resolve(2);
}, 1000);
});
}
function getB(){
return new Promise(function(resolve, reject){
setTimeout(function(){
resolve(3);
}, 1000);
});
}
function addAB(a,b){
return a+b
}
function getResult(){
var obj={};
Promise.resolve().then(function(){
return getA()
})
.then(function(a){
obj.a=a;
})
.then(function(){
return getB()
})
.then(function(b){
obj.b=b;
return obj;
})
.then(function(obj){
return addAB(obj['a'],obj['b'])
})
.then(data=>{
console.log(data)
})
.catch(e => console.log(e));
}
getResult();
- 2.2 方法二——使用promise构建队列
function getResult(){
var res=[];
// 构建队列
function queue(arr) {
var sequence = Promise.resolve();
arr.forEach(function (item) {
sequence = sequence.then(item).then(data=>{
res.push(data);
return res
})
})
return sequence
}
// 执行队列
queue([getA,getB]).then(data=>{
return addAB(data[0],data[1])
})
.then(data => {
console.log(data)
})
.catch(e => console.log(e));
}
getResult();
- 2.3方法三——使用async、await实现类似同步编程
function getResult(){
async function queue(arr) {
let res = []
for (let fn of arr) {
var data= await fn();
res.push(data);
}
return await res
}
queue([getA,getB])
.then(data => {
return addAB(data[0],data[1])
}).then(data=>console.log(data))
}
3. 总结
实现异步队列函数的三种方式
方法一——连续使用then链式操作
方法二——使用promise构建队列
方法三——使用async、await实现类似同步编程,async函数内部实现同步
参考
Promise的顺序执行和并行执行
构建Promise队列实现异步函数顺序执行
补充
ES6 Promise中断
中断或取消Promise链的可行方案
(1)reject()
(2)throw new Error()
一般来说,不要在then方法里面定义 reject 状态的回调函数(即then的第二个参数),总是使用catch方法。
当 async/await 遇上 forEach
当 async/await 遇上 forEach
在文章 ES7 中的 async await 中介绍了 async/await。本文将分析介绍当 async/await 遇上 forEach
出现的一些问题和解决方案。
问题描述
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
var getNumbers = () => {
return Promise.resolve([1, 2, 3])
}
var multi = num => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (num) {
resolve(num * num)
} else {
reject(new Error('num not specified'))
}
}, 1000)
})
}
async function test () {
var nums = await getNumbers()
nums.forEach(async x => {
var res = await multi(x)
console.log(res)
})
}
test()
|
在这个例子中,通过 forEach
遍历的将每一个数字都执行 multi
操作。代码执行的结果是:1 秒后,一次性输出1,4,9。这个结果和我们的预期有些区别,我们是希望每间隔 1 秒,然后依次输出 1,4,9;所以当前代码应该是并行执行了,而我们期望的应该是串行执行。
问题分析
JavaScript 中的循环数组遍历
在 JavaScript 中提供了如下四种循环遍历数组元素的方式:
-
for
这是循环遍历数组元素最简单的方式123for(i = 0; i < arr.length; i++) {console.log(arr[i]);} -
for-in
for-in
语句以任意顺序遍历一个对象的可枚举属性,对于数组即是数组下标,对于对象即是对象的 key 值。注意for-in
遍历返回的对象属性都是字符串类型,即使是数组下标,也是字符串 “0”, “1”, “2” 等等。[不推荐使用for-in
语句]123for (var index in myArray) {console.log(myArray[index]);} -
forEach
forEach
方法用于调用数组的每个元素,并将元素传递给回调函数;注意在回调函数中无法使用break
跳出当前循环,也无法使用return
返回值123myArray.forEach(function (value) {console.log(value);}); -
for-of
for-of
语句为各种 collection 集合对象专门定制的,遍历集合对象的属性值,注意和for-in
的区别123for (var value of myArray) {console.log(value);}
分析问题
在本例中 forEach
的回调函数是一个异步函数,异步函数中包含一个 await
等待 Promise 返回结果,我们期望数组元素串行执行这个异步操作,但是实际却是并行执行了。
forEach
的 polyfill 参考:MDN-Array.prototype.forEach(),简单点理解:
1
2
3
4
5
6
7
|
Array.prototype.forEach = function (callback) {
// this represents our array
for (let index = 0; index < this.length; index++) {
// We call the callback for each entry
callback(this[index], index, this)
}
}
|
相当于 for
循环执行了这个异步函数,所以是并行执行,导致了一次性全部输出结果:1,4,9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
async function test () {
var nums = await getNumbers()
// nums.forEach(async x => {
// var res = await multi(x)
// console.log(res)
// })
for(let index = 0; index < nums.length; index++) {
(async x => {
var res = await multi(x)
console.log(res)
})(nums[index])
}
}
|
解决问题
方式一
我们可以改造一下 forEach
,确保每一个异步的回调执行完成后,才执行下一个
1
2
3
4
5
6
7
8
9
10
11
12
13
|
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
}
async function test () {
var nums = await getNumbers()
asyncForEach(nums, async x => {
var res = await multi(x)
console.log(res)
})
}
|
方式二
使用 for-of
替代 for-each
。
for-of
可以遍历各种集合对象的属性值,要求被遍历的对象需要实现迭代器 (iterator) 方法,例如 myObject[Symbol.iterator]()
用于告知 JS 引擎如何遍历该对象。一个拥有 [Symbol.iterator]()
方法的对象被认为是可遍历的。
1
2
3
4
5
6
7
8
|
var zeroesForeverIterator = {
[Symbol.iterator]: function () {
return this;
},
next: function () {
return {done: false, value: 0};
}
};
|
如上就是一个最简单的迭代器对象;for-of
遍历对象时,先调用遍历对象的迭代器方法 [Symbol.iterator]()
,该方法返回一个迭代器对象(迭代器对象中包含一个 next
方法);然后调用该迭代器对象上的 next
方法。
每次调用 next
方法都返回一个对象,其中 done
和 value
属性用来表示遍历是否结束和当前遍历的属性值,当 done
的值为 true
时,遍历就停止了。
1
2
3
|
for (VAR of ITERABLE) {
STATEMENTS
}
|
等价于:
1
2
3
4
5
6
7
|
var $iterator = ITERABLE[Symbol.iterator]();
var $result = $iterator.next();
while (!$result.done) {
VAR = $result.value;
STATEMENTS
$result = $iterator.next();
}
|
由此可以知道 for-of
和 forEach
遍历元素时处理的方式是不同的。使用 for-of
替代 for-each
后代码为:
1
2
3
4
5
6
7
|
async function test () {
var nums = await getNumbers()
for(let x of nums) {
var res = await multi(x)
console.log(res)
}
}
|
JavaScript 循环:如何处理 async/await
简评:作者提供了在 JavaScript 循环中处理 async/await 的解决方案,可以参考参考。
同步循环
很久以前我写的循环是这样的:
后来 JavaScript 提供了很多新的特性,现在我们会更倾向于用下面这种写法:
在开发过程可能会有这么一种需求,我们需要在循环中异步处理 item,那么可以怎么做呢?
异步循环
如何在循环中使用 await?我们试着写一个异步函数,然后 await 每一次循环任务。
这个代码会抛出一个错误,因为我们不能在同步方法中使用 await, processArray 确实是异步函数,但是 array.forEach 里的匿名函数是同步的。
1. 不要等待结果
要处理这个问题,我们可以把这个匿名函数定义为异步的:
但是这样的话 forEach 方法就相当于异步的了,不会等待遍历完所有的 item,例如下面这段代码:
将会输出:
Done!
1
2
3
如果你不需要等待这个循环完成,这样就已经可以了。但是大部分情况我们还是需要等待这个循环完成才进行之后的操作。
2. 串行遍历
要等待所有的结果返回,我们还是要回到老式的 for 循环写法:
最后的结果符合我们的预期:
1
2
3
Done!
上面这段的遍历代码是串行执行的,我们也可以把它换成并行的。
3. 并行遍历
我们可以稍微更改上面的代码来编程并行的:
(注意:对于特别大的数组不建议使用这种写法,太多的并行任务会加重 CPU 和内存的负荷)
原文:JavaScript loops — how to handle async/await
推荐阅读:前端开发备忘录(合集)
欢迎关注:知乎专栏「极光日报」,每天为 Makers 导读三篇优质英文文章。
async/await 原理及执行顺序分析
之前写了篇文《这一次,彻底理解Promise原理》,剖析了Promise的相关原理,反应不错,这次把学习到的相关的知识也写下。
我们都知道,Promise解决了回调地狱的问题,但是如果遇到复杂的业务,代码里面会包含大量的 then 函数,使得代码依然不是太容易阅读。
基于这个原因,ES7 引入了 async/await,这是 JavaScript 异步编程的一个重大改进,提供了在不阻塞主线程的情况下使用同步代码实现异步访问资源的能力,并且使得代码逻辑更加清晰,而且还支持 try-catch 来捕获异常,非常符合人的线性思维。
所以,要研究一下如何实现 async/await。总的来说,async 是Generator函数的语法糖,并对Generator函数进行了改进。
Generator函数简介
Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态,但是只有调用next
方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield
表达式就是暂停标志。
有这样一段代码:
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
复制代码
调用及运行结果:
hw.next()// { value: 'hello', done: false }
hw.next()// { value: 'world', done: false }
hw.next()// { value: 'ending', done: true }
hw.next()// { value: undefined, done: true }
复制代码
由结果可以看出,Generator函数
被调用时并不会执行,只有当调用next方法
、内部指针指向该语句时才会执行,即函数可以暂停,也可以恢复执行
。每次调用遍历器对象的next方法,就会返回一个有着value
和done
两个属性的对象。value
属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done
属性是一个布尔值,表示是否遍历结束。
Generator函数暂停恢复执行原理
要搞懂函数为何能暂停和恢复,那你首先要了解协程的概念。
一个线程(或函数)执行到一半,可以暂停执行,将执行权交给另一个线程(或函数),等到稍后收回执行权的时候,再恢复执行。这种可以并行执行、交换执行权的线程(或函数),就称为协程。
协程是一种比线程更加轻量级的存在。普通线程是抢先式的,会争夺cpu资源,而协程是合作的,可以把协程看成是跑在线程上的任务,一个线程上可以存在多个协程,但是在线程上同时只能执行一个协程。它的运行流程大致如下:
- 协程
A
开始执行 - 协程
A
执行到某个阶段,进入暂停,执行权转移到协程B
- 协程
B
执行完成或暂停,将执行权交还A
- 协程
A
恢复执行
协程遇到yield命令
就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作,如果去除yield命令,简直一模一样。
执行器
通常,我们把执行生成器的代码封装成一个函数,并把这个执行生成器代码的函数称为执行器,co 模块
就是一个著名的执行器。
Generator 是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。两种方法可以做到这一点:
- 回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
- Promise 对象。将异步操作包装成 Promise 对象,用then方法交回执行权。
一个基于 Promise 对象的简单自动执行器:
function run(gen){
var g = gen();
function next(data){
var result = g.next(data);
if (result.done) return result.value;
result.value.then(function(data){
next(data);
});
}
next();
}
复制代码
我们使用时,可以这样使用即可,
function* foo() {
let response1 = yield fetch('https://xxx') //返回promise对象
console.log('response1')
console.log(response1)
let response2 = yield fetch('https://xxx') //返回promise对象
console.log('response2')
console.log(response2)
}
run(foo);
复制代码
上面代码中,只要 Generator 函数还没执行到最后一步,next函数就调用自身,以此实现自动执行。通过使用生成器配合执行器,就能实现使用同步的方式写出异步代码了,这样也大大加强了代码的可读性。
async/await
ES7 中引入了 async/await,这种方式能够彻底告别执行器和生成器,实现更加直观简洁的代码。根据 MDN 定义,async 是一个通过异步执行并隐式返回 Promise 作为结果的函数。可以说async 是Generator函数的语法糖,并对Generator函数进行了改进。
前文中的代码,用async
实现是这样:
const foo = async () => {
let response1 = await fetch('https://xxx')
console.log('response1')
console.log(response1)
let response2 = await fetch('https://xxx')
console.log('response2')
console.log(response2)
}
复制代码
一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
async函数对 Generator 函数的改进,体现在以下四点:
内置执行器
。Generator 函数的执行必须依靠执行器,而 async 函数自带执行器,无需手动执行 next() 方法。更好的语义
。async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。更广的适用性
。co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。返回值是 Promise
。async 函数返回值是 Promise 对象,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用。
这里的重点是自带了执行器,相当于把我们要额外做的(写执行器/依赖co模块)都封装了在内部。比如:
async function fn(args) {
// ...
}
复制代码
等同于:
function fn(args) {
return spawn(function* () {
// ...
});
}
function spawn(genF) { //spawn函数就是自动执行器,跟简单版的思路是一样的,多了Promise和容错处理
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
复制代码
async/await执行顺序
通过上面的分析,我们知道async
隐式返回 Promise 作为结果的函数,那么可以简单理解为,await后面的函数执行完毕时,await会产生一个微任务(Promise.then是微任务)。但是我们要注意这个微任务产生的时机,它是执行完await之后,直接跳出async函数,执行其他代码(此处就是协程的运作,A暂停执行,控制权交给B)。其他代码执行完毕后,再回到async函数去执行剩下的代码,然后把await后面的代码注册到微任务队列当中。我们来看个例子:
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
// script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout
复制代码
分析这段代码:
- 执行代码,输出
script start
。 - 执行async1(),会调用async2(),然后输出
async2 end
,此时将会保留async1函数的上下文,然后跳出async1函数。 - 遇到setTimeout,产生一个宏任务
- 执行Promise,输出
Promise
。遇到then,产生第一个微任务 - 继续执行代码,输出
script end
- 代码逻辑执行完毕(当前宏任务执行完毕),开始执行当前宏任务产生的微任务队列,输出
promise1
,该微任务遇到then,产生一个新的微任务 - 执行产生的微任务,输出
promise2
,当前微任务队列执行完毕。执行权回到async1 - 执行await,实际上会产生一个promise返回,即
let promise_ = new Promise((resolve,reject){ resolve(undefined)})
复制代码
执行完成,执行await后面的语句,输出async1 end
- 最后,执行下一个宏任务,即执行setTimeout,输出
setTimeout
注意
新版的chrome浏览器中不是如上打印的,因为chrome优化了,await变得更快了,输出为:
// script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout
复制代码
但是这种做法其实是违法了规范的,当然规范也是可以更改的,这是 V8 团队的一个 PR ,目前新版打印已经修改。
知乎上也有相关讨论,可以看看 www.zhihu.com/question/26…
参考资料
- 极客时间《浏览器工作原理与实践》
- 阮一峰《es6入门》
Promise资料
最后
- 欢迎加我微信(winty230),拉你进技术群,长期交流学习...
- 欢迎关注「前端Q」,认真学前端,做个有专业的技术人...