深入理解js 中async 函数的含义和用法


Posted in Javascript onMay 13, 2018

一、终极解决

异步操作是 JavaScript 编程的麻烦事,麻烦到一直有人提出各种各样的方案,试图解决这个问题。

从最早的回调函数,到 Promise 对象,再到 Generator 函数,每次都有所改进,但又让人觉得不彻底。它们都有额外的复杂性,都需要理解抽象的底层运行机制。

异步I/O不就是读取一个文件吗,干嘛要搞得这么复杂?异步编程的最高境界,就是根本不用关心它是不是异步。

async 函数就是隧道尽头的亮光,很多人认为它是异步操作的终极解决方案。

二、async 函数是什么?

一句话,async 函数就是 Generator 函数的语法糖。

前文有一个 Generator 函数,依次读取两个文件。

var fs = require('fs');
var readFile = function (fileName){
 return new Promise(function (resolve, reject){
  fs.readFile(fileName, function(error, data){
   if (error) reject(error);
   resolve(data);
  });
 });
};
var gen = function* (){
 var f1 = yield readFile('/etc/fstab');
 var f2 = yield readFile('/etc/shells');
 console.log(f1.toString());
 console.log(f2.toString());
};

写成 async 函数,就是下面这样。

var asyncReadFile = async function (){
 var f1 = await readFile('/etc/fstab');
 var f2 = await readFile('/etc/shells');
 console.log(f1.toString());
 console.log(f2.toString());
};

一比较就会发现,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。

三、async 函数的优点

async 函数对 Generator 函数的改进,体现在以下三点。

(1)内置执行器。 Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。

var result = asyncReadFile();

(2)更好的语义。 async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。

(3)更广的适用性。 co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

四、async 函数的实现

async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。

async function fn(args){
 // ...
}
// 等同于
function fn(args){ 
 return spawn(function*() {
  // ...
 }); 
}

所有的 async 函数都可以写成上面的第二种形式,其中的 spawn 函数就是自动执行器。

下面给出 spawn 函数的实现,基本就是前文自动执行器的翻版。

function spawn(genF) {
 return new Promise(function(resolve, reject) {
  var gen = genF();
  function step(nextF) {
   try {
    var 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 函数是非常新的语法功能,新到都不属于 ES6,而是属于 ES7。目前,它仍处于提案阶段,但是转码器 Babel 和 regenerator 都已经支持,转码后就能使用。

五、async 函数的用法

同 Generator 函数一样,async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。

下面是一个例子。

async function getStockPriceByName(name) {
 var symbol = await getStockSymbol(name);
 var stockPrice = await getStockPrice(symbol);
 return stockPrice;
}
getStockPriceByName('goog').then(function (result){
 console.log(result);
});

上面代码是一个获取股票报价的函数,函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。

下面的例子,指定多少毫秒后输出一个值。

function timeout(ms) {
 return new Promise((resolve) => {
  setTimeout(resolve, ms);
 });
}
async function asyncPrint(value, ms) {
 await timeout(ms);
 console.log(value)
}
asyncPrint('hello world', 50);

上面代码指定50毫秒以后,输出"hello world"。

六、注意点

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);
 });
}

await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。

async function dbFuc(db) {
 let docs = [{}, {}, {}];
 // 报错
 docs.forEach(function (doc) {
  await db.post(doc);
 });
}

上面代码会报错,因为 await 用在普通函数之中了。但是,如果将 forEach 方法的参数改成 async 函数,也有问题。

async function dbFuc(db) {
 let docs = [{}, {}, {}];

 // 可能得到错误结果
 docs.forEach(async function (doc) {
  await db.post(doc);
 });
}

上面代码可能不会正常工作,原因是这时三个 db.post 操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用 for 循环。

async function dbFuc(db) {
 let docs = [{}, {}, {}];
 for (let doc of docs) {
  await db.post(doc);
 }
}

如果确实希望多个请求并发执行,可以使用 Promise.all 方法。

async function dbFuc(db) {
 let docs = [{}, {}, {}];
 let promises = docs.map((doc) => db.post(doc));
 let results = await Promise.all(promises);
 console.log(results);
}
// 或者使用下面的写法
async function dbFuc(db) {
 let docs = [{}, {}, {}];
 let promises = docs.map((doc) => db.post(doc));
 let results = [];
 for (let promise of promises) {
  results.push(await promise);
 }
 console.log(results);
}

总结

以上所述是小编给大家介绍的js 中async 函数的含义和用法,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!

Javascript 相关文章推荐
JS 实现BASE64_ENCODE和BASE64_DECODE(实例代码)
Nov 13 Javascript
javascript函数作用域学习示例(js作用域)
Jan 13 Javascript
javascript制作的网页侧边弹出框思路及实现代码
May 21 Javascript
select多选 multiple的使用示例
Jun 16 Javascript
node.js中的fs.chmodSync方法使用说明
Dec 18 Javascript
BootStrap智能表单实战系列(六)表单编辑页面的数据绑定
Jun 13 Javascript
js删除Array数组中指定元素的两种方法
Aug 03 Javascript
vue 多入口文件搭建 vue多页面搭建的实例讲解
Mar 12 Javascript
Vue父子组件双向绑定传值的实现方法
Jul 31 Javascript
vue 使用html2canvas将DOM转化为图片的方法
Sep 11 Javascript
vue2.* element tabs tab-pane 动态加载组件操作
Jul 19 Javascript
如何使用CocosCreator对象池
Apr 14 Javascript
如何更好的编写js async函数
May 13 #Javascript
基于jQuery实现无缝轮播与左右点击效果
May 13 #jQuery
在angular 6中使用 less 的实例代码
May 13 #Javascript
深入理解JS的事件绑定、事件流模型
May 13 #Javascript
在ES5与ES6环境下处理函数默认参数的实现方法
May 13 #Javascript
vue中的$emit 与$on父子组件与兄弟组件的之间通信方式
May 13 #Javascript
node 命令方式启动修改端口的方法
May 12 #Javascript
You might like
thinkPHP使用post方式查询时分页失效的解决方法
2015/12/09 PHP
PHP实现求解最长公共子串问题的方法
2017/11/17 PHP
Exitjs获取DataView中图片文件名
2009/11/26 Javascript
javascript面向对象的方式实现的弹出层效果代码
2010/01/28 Javascript
json2.js的初步学习与了解
2011/10/06 Javascript
分享XmlHttpRequest调用Webservice的一点心得
2012/07/20 Javascript
JS截取字符串常用方法详细整理
2013/10/28 Javascript
Tab切换组件(选项卡功能)实例代码
2013/11/21 Javascript
Ajax基础知识详解
2017/02/17 Javascript
Vue + Webpack + Vue-loader学习教程之功能介绍篇
2017/03/14 Javascript
详解nodejs爬虫程序解决gbk等中文编码问题
2017/04/06 NodeJs
bootstrap的工具提示实例代码
2017/05/17 Javascript
requirejs按需加载angularjs文件实例
2017/06/08 Javascript
js学习心得_一个简单的动画库封装tween.js
2017/07/14 Javascript
Vue无限滑动周选择日期的组件的示例代码
2018/07/18 Javascript
React实现全局组件的Toast轻提示效果
2018/09/21 Javascript
一个Java程序猿眼中的前后端分离以及Vue.js入门(推荐)
2019/04/19 Javascript
[01:09]DOTA2次级职业联赛 - 99战队宣传片
2014/12/01 DOTA
[01:15:12]DOTA2上海特级锦标赛主赛事日 - 1 败者组第一轮#4Newbee VS CDEC
2016/03/03 DOTA
浅谈Tensorflow由于版本问题出现的几种错误及解决方法
2018/06/13 Python
python错误调试及单元文档测试过程解析
2019/12/19 Python
Django 设置多环境配置文件载入问题
2020/02/25 Python
Python PIL库图片灰化处理
2020/04/07 Python
如何一键升级Python所有包
2020/11/05 Python
Python+Xlwings 删除Excel的行和列
2020/12/19 Python
互动出版网:专业书籍
2017/03/21 全球购物
Ralph Lauren意大利官方网站:时尚界最负盛名的品牌之一
2018/10/18 全球购物
结婚周年感言
2014/02/24 职场文书
《胖乎乎的小手》教学反思
2014/02/26 职场文书
大学学风建设方案
2014/05/04 职场文书
幼儿园小班个人总结
2015/02/12 职场文书
个人自荐书范文
2015/03/09 职场文书
《唯一的听众》教学反思
2016/02/18 职场文书
Go语言特点及基本数据类型使用详解
2022/03/21 Golang
python+pytest接口自动化之token关联登录的实现
2022/04/06 Python
nginx代理实现静态资源访问的示例代码
2022/07/07 Servers