精华 体验异步的终极解决方案-ES7的Async/Await
发布于 8 年前 作者 think2011 211079 次浏览 来自 分享

阅读本文前,期待您对promise和ES6(ECMA2015)有所了解,会更容易理解。 本文以体验为主,不会深入说明,结尾有详细的文章引用。

第一个例子

Async/Await应该是目前最简单的异步方案了,首先来看个例子。

这里我们要实现一个暂停功能,输入N毫秒,则停顿N毫秒后才继续往下执行。

var sleep = function (time) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve();
        }, time);
    })
};

var start = async function () {
    // 在这里使用起来就像同步代码那样直观
    console.log('start');
    await sleep(3000);
    console.log('end');
};

start();

控制台先输出start,稍等3秒后,输出了end

基本规则

  1. async 表示这是一个async函数await只能用在这个函数里面

  2. await 表示在这里等待promise返回结果了,再继续执行。

  3. await 后面跟着的应该是一个promise对象(当然,其他返回值也没关系,只是会立即执行,不过那样就没有意义了…)

获得返回值

await等待的虽然是promise对象,但不必写.then(..),直接可以得到返回值。

var sleep = function (time) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            // 返回 ‘ok’
            resolve('ok');
        }, time);
    })
};

var start = async function () {
    let result = await sleep(3000);
    console.log(result); // 收到 ‘ok’
};

捕捉错误

既然.then(..)不用写了,那么.catch(..)也不用写,可以直接用标准的try catch语法捕捉错误。

var sleep = function (time) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            // 模拟出错了,返回 ‘error’
            reject('error');
        }, time);
    })
};

var start = async function () {
    try {
        console.log('start');
        await sleep(3000); // 这里得到了一个返回错误
        
        // 所以以下代码不会被执行了
        console.log('end');
    } catch (err) {
        console.log(err); // 这里捕捉到错误 `error`
    }
};

循环多个await

await看起来就像是同步代码,所以可以理所当然的写在for循环里,不必担心以往需要闭包才能解决的问题。

..省略以上代码

var start = async function () {
    for (var i = 1; i <= 10; i++) {
        console.log(`当前是第${i}次等待..`);
        await sleep(1000);
    }
};

值得注意的是,await必须在async函数的上下文中的。

..省略以上代码

let 一到十 = [1,2,3,4,5,6,7,8,9,10];

// 错误示范
一到十.forEach(function (v) {
    console.log(`当前是第${v}次等待..`);
    await sleep(1000); // 错误!! await只能在async函数中运行
});

// 正确示范
for(var v of 一到十) {
    console.log(`当前是第${v}次等待..`);
    await sleep(1000); // 正确, for循环的上下文还在async函数中
}

第二个例子

这个例子是一个小应用,根据电影文件名,自动下载对应的海报。

直接贴出代码,就不说明了。

import fs from 'fs';
import path from 'path';
import request from 'request';

var movieDir = __dirname + '/movies',
    exts     = ['.mkv', '.avi', '.mp4', '.rm', '.rmvb', '.wmv'];

// 读取文件列表
var readFiles = function () {
    return new Promise(function (resolve, reject) {
        fs.readdir(movieDir, function (err, files) {
            resolve(files.filter((v) => exts.includes(path.parse(v).ext)));
        });
    });
};

// 获取海报
var getPoster = function (movieName) {
    let url = `https://api.douban.com/v2/movie/search?q=${encodeURI(movieName)}`;

    return new Promise(function (resolve, reject) {
        request({url: url, json: true}, function (error, response, body) {
            if (error) return reject(error);

            resolve(body.subjects[0].images.large);
        })
    });
};

// 保存海报
var savePoster = function (movieName, url) {
    request.get(url).pipe(fs.createWriteStream(path.join(movieDir, movieName + '.jpg')));
};


(async () => {
    let files = await readFiles();

    // await只能使用在原生语法
    for (var file of files) {
        let name = path.parse(file).name;

        console.log(`正在获取【${name}】的海报`);
        savePoster(name, await getPoster(name));
    }

    console.log('=== 获取海报完成 ===');
})();

其他信息

微软的Edge浏览器已经率先支持了async/await语法,相信不久之后chrome等浏览器、node.js也会跟进的,超期待!~(≧▽≦)/~

一些资料和工具

本文同时发布在 think2011的博客 2015-11-09 22:42

83 回复

赞~(≧▽≦)/~

终极未免夸大。。。

你这用户名老让我看成thinkjs

来自炫酷的 CNodeMD

睡前又刷出一片好文章,谢谢分享

mark

来自炫酷的 CNodeMD

@yuu2lee4 哈哈,ThinkJS 就是用 async/await 来解决异步的。

很不错!mark

越来越象python了

所有显式异步都不是终极。。。

没有异步的异步才是终极。。。

终极未免夸大。。。

@wynfrith 新手刚来CNode,一个白痴问题困扰了一下,该怎么mark?没找到mark的按钮啥的啊。。。。轻扇我。。。求告知Orz

博主大大 这个是import xx from xx 是什么写法?有这个文档么?、

@LastKing es6 的写法啊

看了一下,感觉很牛逼!确实比很多方案感觉要好。

@dd1994 我对ES的这些标准不太熟悉,受教了

c#即视感。。

还是离不开promise呀

但是有一个问题解决不了,就是当有两个await语句是,这两个是顺序执行,但是这两个是可以同步执行的,所以还是靠promise包装一次才行

来自炫酷的 CNodeMD 越来越喜欢material design😁

不错,比generator好,不知道现在有没有库可以兼容

@WilliamDu1981 可以通过 Babel 编译来使用

@XGHeaven 顺序和同步执行的例子:

function sleep(ms) {
  return new Promise((resolve, reject) => {
    console.log(`sleep ${ms}`);
    setTimeout(_ => resolve(ms), ms);
  });
}

(async () => {
  
  // 按照顺序一个一个来
  let a = await sleep(20);
  let b = await sleep(30);
  
  // 同时来
  let c = await Promise.all([sleep(40), sleep(50), sleep(60)]);
  
  console.log('done', a, b, c);
  
})();

终极武器, 人间大炮

@leizongmin 还是要基于promise,es7没有原生提供 😂

来自炫酷的 CNodeMD 越来越喜欢material design😁

请问 一直在说koa2支持Async/Await koa v2会自动把一般的函数处理成promise返回吗? 知道了koa2的中间件流程以及函数内部流程

async 已经在 v8 landed!

貌似edge没有支持async吧,在里面跑会报错!

还是要返回promise?

看到try catch就没兴趣了,好丑陋的写法

用心,mark

网页端不建议使用 async await,因为那个 regenertor 依赖包太大了,Promise 的 polyfill 小得多。

我一直不太看好async await

我倒没觉得现在的异步处理方式有什么问题,写好一个方法等待回调,从代码组织顺序上更让人容易将这个过程理解为异步实现,从代码解读上感觉也比promise、generator、Async/Await更易读,毕竟代码首先是写给人看的! 以为了把一个异步的处理过程以同步的代码组织方式展示而摆脱所谓的“回调地狱”为目的,在其中间要引入这么多的其他概念增加代码理解难度,反而觉得有点本末倒置了! 一家之言,不喜轻喷! - -

@AserSayHi 掌握以后会方便,这便是知识呀

@William17 async函数的返回值是一个promise对象。这个被返回的proimse对象的状态(即,是resolved,还是rejected)取决于async函数是如何结束的:

  1. async函数正常地通过return语句返回值 修改promise状态为: promise.resolve( 《return出来的值》 );
  2. async函数内部有异常被抛出 修改promise状态为: promise.reject(《throw出来的Error对象》)
  3. async函数没有返回值 修改promise状态为: promise.resolve()

马克加索尔!

如果 在 resolve中出现了两个或者多个传入的值,那么返回给await后怎么获取??还是说我们就想写模块一样,讲上述的各种回调函数按照细微的小模块,写成一个一个的function,那么在最后一个 匿名执行函数中 再用 async和await 然后像组合一样在最后这个 匿名函数中将无数个 resolve返回的值汇总,那么每个resolve中只放入一个值?不是很懂,请教大家。

await 和yield有什么区别?

@think2011 ,如果让同时读取100个电影,每个进程都是读取 保存这种顺序的排列,如果做到并发呢?就是同时读取,难道要写100然后使用Promise.all 吗?求教。话说nodejs 的进程是单进程,Promise.all 是怎么做到并发的?

@ThomasHuke 利用 setTimeout, process.nextTick , setImmdiate ,函数执行异步IO 操作 promise.all 相当于灯所有的 异步事件执行后才会自动执行触发的,函数而已;

node 做出响应,依赖于 response.write response.end ; 不是依赖于主线程。

有个疑问,楼主是如何在node中使用了es6的import,并且演示的运行命令直接用node xxx.js跑的?请指教

很有帮助

不错,mark

@xuminke node7.6+ 已经开始支持async/await语法了

@i5ting 正解,js领域现在谁说什么什么是终极方案估计都会很快被打脸。变化太快了。当初我找到 async 库我就认为是终极方案了。写着写着觉得也有不好的地方。

@xuminke 我的建议,目前哪怕你已经全面开始写es6了,也不要用 import,还是继续使用 require

@ThomasHuke async/await 比较常用的还是处理api,每个请求都不会太重。命令行任务脚本用这种方式必死,有1M 部电影要怎么弄?直接全部堵死。所有命令行大任务还是用 async.mapLimit 来处理是正道。

@stonephp promise 库 也有 mapLimit

例如 bluebird.map ...map(..., {concurrency: 3});

这样就可以配合 async/await

当然 async.js 也有 promise-async 至于 import 现在 不用 babel 也实现不了. 好像 edge 可以原生支持

@yinzSE 我主要是服务端编程,所以直接放弃掉了babel。

看你那张gif图是直接执行了node bootstrap.js,这个bootstrap.js是经过babel之后的吗? ES7好像有些语法直接node不能执行的吧?

虽然多个异步也不需要嵌套了,不过还是要依赖promise 没有意思。就少写了.then和.catch。但是错误捕获需要用到try catch。

异步函数的错误处理机制能一直向上向调用方抛出直到try catch块吗?

async function er() {
     throw new Error()
}
async function test() {
   //  try{
       await er()
   // }catch(e){
   //    console.log('内层错误')
   // }
}
try{
   test()
}catch(e){
  debug('外层错误')
}

我测试时只有内层才能捕获错误,外层捕获不了,直接抛出错误到进程 (node:20136) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error (node:20136) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

好文

来自酷炫的 CNodeMD

@holyselina 没理解在er前面加async有何意义,还有await er()又有何意义?

@holyselina 异步函数返回的是一个promise对象(即使throw error也是返回一个reject的promise),在外层应该使用test.catch()来捕获

await的作用是把promise对象转换成结果(resolve/reject的内容),所以能用try方式捕获(用.catch()反而无法捕获)

把er前面的await去掉就变成需要用.catch捕获错误的promise对象了

@Ireoo 1.我只是模拟同步抛出错误,er函数正常业务应该返回一个Promise,我为了简写所以没写其他代码. 2.如果在函数在返回Promise之前如果出现了同步错误调用方就捕获不了错误.在Promise的then方法传入的函数中,在返回Promise之前抛出同步错误是可以被后续捕获的.

@dislido 如果我在异步函数返回Promise之前发生了同步错误(不管是什么原因,肯定是意料之外的)外层的调用方就捕获不了错误,但是相同的情况使用Promise.then的形式则可以捕获,在Promise的then方法传入的函数中,在返回Promise之前抛出同步错误是可以被后续捕获的.

@holyselina 最好是返回错误,而不是抛出错误 From Noder

问个比较小白的例子: 假如在第一个例子中,加入以下代码: start(); console.log(‘done’);

输出结果: start done end

这种看着同步的方式,难道上层都得是 async?

谢谢!

@Daniel-yim 好的吧,自己回答自己,已经找到答案了,await 返回的是一个Promise。

弱鸡表示:等等,我ES6还能玩好几年

@XGHeaven 我觉得这个同时并发执行可以手动来封装做操作,因为有时候 下面的一个操作是依赖上面的返回值的 这种情况。

我想问一下,当一个函数A 有可能是 async 函数(也就是promise 对象),也有可能是 普通的函数的时候 我用 await A() 正确吗? 我试验的结果是没有问题,无论A 是async 函数,还是正常 同步函数,await 都能正常。有没有什么隐藏的不一样的地方?

看到海报的例子发现之前竟然看过一遍了,这脑子。。

菜鸟求问,ES7的async和npm上的async是什么区别啊?

写的很好,很清楚。 如果是函数式编程,用compose函数,函数链中有异步函数,应该怎么写?

这篇文章,只是分享了一个海报下载的代码,怎么就精华了呢 ?

回到顶部