javascript基础学习系列三百八十二:异步函数

异步函数,也称为“async/await”(语法关键字),是 ES6 期约模式在 ECMAScript 函数中的应用。async/await 是 ES8 规范新增的。这个特性从行为和语法上都增强了 JavaScript,让以同步方式写的代码能够异步执行。下面来看一个最简单的例子,这个期约在超时之后会解决为一个值:

这个期约在 1000 毫秒之后解决为数值 3。如果程序中的其他代码要在这个值可用时访问它,则需要写一个解决处理程序:

p.then((x) => console.log(x)); // 3 

这其实是很不方便的,因为其他代码都必须塞到期约处理程序中。不过可以把处理程序定义为一个函数:

let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3)); 
p.then(handler); // 3 

这个改进其实也不大。这是因为任何需要访问这个期约所产生值的代码,都需要以处理程序的形式来接收这个值。也就是说,代码照样还是要放到处理程序里。ES8 为此提供了 async/await 关键字。
异步函数
ES8 的 async/await 旨在解决利用异步结构组织代码的问题。为此,ECMAScript 对函数进行了扩展,为其增加了两个新关键字:async 和 await。
async
async 关键字用于声明异步函数。这个关键字可以用在函数声明、函数表达式、箭头函数和方法上:

let bar = async function() {}; 
let baz = async () => {}; 
class Qux { 
 async qux() {} 
} 

使用 async 关键字可以让函数具有异步特征,但总体上其代码仍然是同步求值的。而在参数或闭包方面,异步函数仍然具有普通 JavaScript 函数的正常行为。正如下面的例子所示,foo()函数仍然会在后面的指令之前被求值:

 console.log(1); 
} 
foo(); 
console.log(2); 
// 1 
// 2 

不过,异步函数如果使用 return 关键字返回了值(如果没有 return 则会返回 undefined),这个值会被 Promise.resolve()包装成一个期约对象。异步函数始终返回期约对象。在函数外部调用这个函数可以得到它返回的期约:

 console.log(1); 
 return 3; 
} 
// 给返回的期约添加一个解决处理程序
foo().then(console.log);
console.log(2); 
// 1 
// 2 
// 3 
当然,直接返回一个期约对象也是一样的:
async function foo() { 
 console.log(1); 
 return Promise.resolve(3); 
} 
// 给返回的期约添加一个解决处理程序
foo().then(console.log); 
console.log(2); 
// 1 
// 2 
// 3

异步函数的返回值期待(但实际上并不要求)一个实现 thenable 接口的对象,但常规的值也可以。
如果返回的是实现 thenable 接口的对象,则这个对象可以由提供给 then()的处理程序“解包”。如果不是,则返回值就被当作已经解决的期约。下面的代码演示了这些情况:

async function foo() { 
 return 'foo'; 
} 
foo().then(console.log); 
// foo
// 返回一个没有实现 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
与在期约处理程序中一样,在异步函数中抛出错误会返回拒绝的期约:
async function foo() { 
 console.log(1); 
 throw 3; 
} 
// 给返回的期约添加一个拒绝处理程序
foo().catch(console.log);
console.log(2); 
// 1 
// 2 
// 3

不过,拒绝期约的错误不会被异步函数捕获:

 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关键字可以暂停异步函数代码的执行,等待期约解决。来看下面这个本章开始就出现过的例子:

p.then((x) => console.log(x)); // 3 

使用 async/await 可以写成这样:

 let p = new Promise((resolve, reject) => setTimeout(resolve, 1000, 3)); 
 console.log(await p); 
} 
foo(); 
// 3

注意,await 关键字会暂停执行异步函数后面的代码,让出 JavaScript 运行时的执行线程。这个行为与生成器函数中的 yield 关键字是一样的。await 关键字同样是尝试“解包”对象的值,然后将这个值传给表达式,再异步恢复异步函数的执行。
await 关键字的用法与 JavaScript 的一元操作一样。它可以单独使用,也可以在表达式中使用,如下面的例子所示:

async function foo() { 
 console.log(await Promise.resolve('foo')); 
} 
foo(); 
// foo 
// 异步打印"bar" 
async function bar() { 
 return await Promise.resolve('bar'); 
} 
bar().then(console.log); 
// bar 
// 1000 毫秒后异步打印"baz" 
async function baz() { 
 await new Promise((resolve, reject) => setTimeout(resolve, 1000)); 
 console.log('baz'); 
} 
baz(); 
// baz(1000 毫秒后)

await 关键字期待(但实际上并不要求)一个实现 thenable 接口的对象,但常规的值也可以。如果是实现 thenable 接口的对象,则这个对象可以由 await 来“解包”。如果不是,则这个值就被当作
已经解决的期约。下面的代码演示了这些情况:

async function foo() { 
 console.log(await 'foo'); 
} 
foo(); 
// foo
// 等待一个没有实现 thenable 接口的对象
async function bar() { 
 console.log(await ['bar']); 
} 
bar(); 
// ['bar']
// 等待一个实现了 thenable 接口的非期约对象
async function baz() { 
 const thenable = { 
 then(callback) { callback('baz'); } 
 }; 
 console.log(await thenable); 
} 
baz(); 
// baz 
// 等待一个期约
async function qux() { 
 console.log(await Promise.resolve('qux')); 
} 
qux(); 
// qux
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值