参考资料:理解javascript中的async/await,感谢原文作者的总结,本文在理解的基础上做了一点小小的修改,主要为了加深自己的知识点掌握
学完了promise,我们知道可以用then链来解决多层回调问题,但是这还不是最理想的操作,我们需要调用很多个then链才能达到要求,那么有没有一种更简便代码量更少的方式达到then链相同的结果呢?asynv和await就很好地解决了这个问题,首先用async声明一个异步函数,然后再用await等待异步结果,把以前then链的结果放到直接放在await,非常方便。
那么,async和await原理是什么呢?为什么可以用这样的语法来优化then链呢?
async/await其实是promise的语法糖,它能实现的效果都能用then链来实现,这也和我们之前提到的一样,它是为优化then链而开发出来的。从字面上来看,async是“异步”的简写,await译为等待,所以我们很好理解async声明function是异步的,await等待某个操作完成。当然语法上强制规定await只能出现在asnyc函数中,我们先来看看async函数返回了什么:
async function testasy(){ return 'hello world'; } let result = testasy(); console.log(result)
这个async声明的异步函数把return后面直接量通过promise.resolve()返回promise对象,所以如果这个最外层没有用await调用的话,是可以用原来then链的方式来调用的:
async function testasy(){ return 'hello world' } let result = testasy() console.log(result) result.then(v=>{ console.log(v) //hello world })
联想一下promise特点——异步无等待,所以当没有await语句执行async函数,它就会立即执行,返回一个promise对象,非阻塞,与普通的promise对象函数一致。
重点就在await,它等待什么呢?
按照语法说明,await等待的是一个promise对象,或者是其他值(也就是说可以等待任何值),如果等待的是promise对象,则返回promise的处理结果;如果是其他值,则返回该值本身。并且await会暂停当前async function的执行,等待promise的处理完成。若promise正常处理(fulfillded),其将回调的resolve函数参数作为await表达式的值,继续执行async function;若promise处理异常(rejected),await表达式会把promise异常原因抛出;另外如果await操作符后面的表达式不是一个promise对象,则返回该值本身。
我们来详细说明一下async/await的作用。await操作符后面可以是任意值,当是promise对象的时候,会暂停async function执行。也就是说,必须得等待await后面的promise处理完成才能继续:
function testasy(x){ return new promise(resolve=>{settimeout(() => { resolve(x); }, 3000) } ) } async function testawt(){ let result = await testasy('hello world'); console.log(result); // 3秒钟之后出现hello world } testawt();
await 表达式的运算结果取决于它等的东西。
如果它等到的不是一个 promise 对象,那 await 表达式的运算结果就是它等到的东西。
如果它等到的是一个 promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
我们再把上面的代码修改一下,好好体会“阻塞”这个词
function testasy(x){ return new promise(resolve=>{settimeout(() => { resolve(x); }, 3000) } ) } async function testawt(){ let result = await testasy('hello world'); console.log(result); // 3秒钟之后出现hello world console.log('tangj') // 3秒钟之后出现tangj } testawt(); console.log('tangsir') //立即输出tangsir
这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 promise 对象中异步执行。await暂停当前async的执行,所以'tangsir''最先输出,hello world'和‘tangj’是3秒钟后同时出现的。
上面已经说明了 async 会将其后的函数(函数表达式或 lambda)的返回值封装成一个 promise 对象,而 await 会等待这个 promise 完成,并将其 resolve 的结果返回出来。
现在举例,用 settimeout模拟耗时的异步操作,先来看看不用 async/await 会怎么写
function takelongtime() { return new promise(resolve => { settimeout(() => resolve("long_time_value"), 1000); }); } takelongtime().then(v => { console.log("got", v); //一秒钟后输出got long_time_value });
如果改用 async/await 呢,会是这样
function takelongtime() { return new promise(resolve => { settimeout(() => resolve("long_time_value"), 1000); }); } async function test() { const v = await takelongtime(); console.log(v); // 一秒钟后输出long_time_value } test();
tanklongtime()本身就是返回的 promise 对象,所以加不加 async结果都一样。
前面我们说了,async和await是处理then链的语法糖,现在我们来看看具体是怎么实现的:
假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用settimeout来模拟异步操作:
/** * 传入参数 n,表示这个函数执行的时间(毫秒) * 执行的结果是 n + 200,这个值将用于下一步骤 */ 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); }
现在用 promise 方式来实现这三个步骤的处理。
function doit(){ console.time('doit'); let time1 = 300; step1(time1) .then((time2) => step2(time2)) .then((time3) => step3(time3)) .then((result) => { console.log(`result is ${result}`); console.timeend("doit"); }) } doit(); //执行结果为: //step1 with 300 //step2 with 500 //step3 with 700 //result is 900 //doit: 1510.2490234375ms
输出结果 result
是 step3()
的参数 700 + 200
= 900
。doit()
顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500
毫秒,和 console.time()/console.timeend()
计算的结果一致。
如果用 async/await 来实现呢,会是这样:
async function doit() { console.time('doit'); let time1 = 300; let time2 = await step1(time1);//将promise对象resolve(n+200)的值赋给time2 let time3 = await step1(time2); let result = await step1(time3); console.log(`result is ${result}`); console.timeend('doit'); } doit(); //执行结果为: //step1 with 300 //step2 with 500 //step3 with 700 //result is 900 //doit: 1512.904296875ms
显然我们用async/await简单多了。
await 命令后面的 promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中。
async function myfunction() { try { await somethingthatreturnapromise(); } catch (err){ console.log(err); } } //另一种写法 async function myfunction() { await somethingthatreturnapromise().catch(function(err) { console.log(err); }) }
如对本文有疑问, 点击进行留言回复!!
offset、client、scroll (width,height、left,top、X,Y)
网友评论