异步函数
- 异步函数,也称为async/await(语法关键字),是es6promise在JS函数中的应用,这个特性从行为和语法上都增强了JS,让以同步方式写的代码能够异步执行
例子1:这个期约在超时之后会解决为一个值,如果程序中其他代码要访问这个值,则需要一个解决处理函数
let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3)); p.then((x) => console.log(x)); // 3
async
- async关键字用于声明异步函数,这个关键之可以用在函数声明,函数表达式,箭头函数和方法上,这个关键字可以让函数具有异步的特性,但总体上代码任然是同步求值得,在参数和闭包方面,异步函数任然具有普通JS函数的正常行为
async function foo() { console.log(1); return 3; } // 给返回的期约添加一个解决处理程序 foo().then(console.log); console.log(4); console.log(5); // 1 ,4 ,5 , 3
小结:在异步函数如果使用了return返回了值,这个值会被promise.resolve()包装成一个期约函数,异步函数始终返回期约对象,在函数外部调用可以得到他返回的期约
- 异步函数的返回值期待一个thenable接口的对象,这个对象可以提供给then()的处理程序解包,如果不是,就会被当做已经解决期约结果
// 返回一个没有实现thenable接口的对象 async function bar() { return ['bar']; } bar().then(console.log); // ['bar'] // 返回一个实现了thenable接口的非期约对象 async function baz() { const thenable = { then(callback) { callback('baz'); } }; return thenable; } baz().then(console.log); // baz // 返回一个期约 async function qux() { return Promise.resolve('qux'); } qux().then(console.log); // qux
小结:异步函数返回值,含thenable则提供then的处理程序解包,否则把返回值当期约处理结果,但是要注意,拒绝期约的错误不会被异步函数捕获
async function foo() { console.log(1); Promise.reject(3); } // Attach a rejected handler to the returned promise foo().catch(console.log); console.log(2); // 1 // 2 // Uncaught (in promise): 3
await
- 因为异步函数主要针对不会马上完成的任务,所以需要一种暂停和恢复执行的能力,使用await可以暂停异步函数的代码执行,等待期约解决
//异步写法 let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3)); p.then((x) => console.log(x)); // 3 //async写法 async function foo() { let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3)); console.log(await p); }
小结:await关键字会暂停执行异步函数后面的代码,让出JS运行时的执行线程,这个行为和生成器中的yield关键字一样,「await关键字同样是尝试解包对象的值,然后将这个值传达给表达式,,再异步恢复异步函数的执行」
接下来看一个涉及停止与恢复执行的例子:这个例子非常有意思,虽然现在你看不到这种效果,但是能更直白帮你理解await
-
为了更好的理解这个例子,首先要知道这几个问题
-
await的地方,等于局部暂停,等待返回值,继续其他线程
-
同样的await按照先后顺序从上往下
-
当await有多个进程以后,会按照多列顺序执行
例子1:
-
async function foo() { console.log(await Promise.resolve('foo')); } async function bar() { console.log(await 'bar'); } async function baz() { console.log('baz'); } foo(); bar(); baz(); // baz // bar // foo
小结:正常情况是这个顺序,「但是你在浏览器跑的时候,结果bar和foo的值会调换位子」,
首先我们说一下执行原理:
- foo()调用时,foo在消息队列添加两列进程,第一列等待返回值,第二列拿到返回值后执行的进程,
- 然后继续bar()这时候在消息队列第一列添加一个进程,这时候执行baz,直接打印出‘baz’,
- 然后开始消息队列进程,foo第一列期约得出结果拿到返回值,然后继续bar的消息队列第一列进程,这时候打印出’bar’,这时候消息队列第一列进程全部执行完毕,
- 进入第二列,foo用第一列进程拿到的返回值执行进程,打印foo,
其次我们说一下为什么浏览器的结果不是这样:1TC39 对 await 后面是期约的情况如何处理做过一次修改。修改后,本例中的 foo()里的await Promise.resolve(’foo‘)只会生成一个异步任务。所以他执行的时候没有两列,只有一列,所以会按await顺序排列
- 按照上面理解,我们来看最终的一个例子
async function foo() { console.log(2); console.log(await Promise.resolve(8)); console.log(9); } async function bar() { console.log(4); console.log(await 6); console.log(7); } console.log(1); foo(); console.log(3); bar(); console.log(5); // 1 // 2 // 3 // 4 // 5 //8 // 9 // 6// 7
小结:上面显示的最新修改过后的结果,按照原结果进程应该是
- 打印1,运行foo,打印2,消息队列加入两列,打印3,运行bar,打印4,消息队列加一列进程,打印5,开始执行消息队列第一行,foo拿到返回值,bar打印6,然后打印7,回到foo第二列,打印结果8,再打印9
本文使用 mdnice 排版