async/await 一种非常丝滑的异步语法


async是es7提出的一种新的异步语法. 一开始es为了解决异步,使用的是promise, 但看到满屏的then之后,就感觉自己傻逼了. 后来提出了generator, 在底层实现了一个异步的模式, 但需要手动执行. 关于如何使用generator,可以参考,how to use generator. 本文这里,不探讨怎么使用generato. 而是,如果使用generator和promise 构造出async的表达. 文末后面会介绍如何正式的使用 async/await,以及里面有什么需要注意的内容。

简单异步实现

一般异步的写法就是,传回调嘛,比如:

var ajax = function(url,cb){
    // send url
    // get res
    ...
    cb(JSON.parse(result))
}

这样,应该最容易写成callback hell. 然后我们引入: generator

function *main() {
    var result1 = yield ajax( "http://some.url.1" );
    var data = JSON.parse( result1 );

    var result2 = yield ajax( "http://some.url.2?id=" + data.id );
    var resp = JSON.parse( result2 );
}

var async = main();
async.next();

function ajax(url){
    // send url
    async.next(res);
}

伪代码的格式如上所述. 这里,需要主意一个点. 如果,你没有在next()中传入res,like async.next(). 那么,里面result1和result2获得的结果就是undefined. 上面就是基本的generator异步. 那如果使用generator来模拟async呢? 这估计得解释一下async出现的原因

async why?

根据上面的解析,我们可以了解到, 使用next 执行语句时, 只能执行yield后面的表达式. 这样造成的结果就是,不能parallel异步呀. 这样限制性还是很大的。所以,为了解决这个问题,使用到es6提出的Promise对象来进行补充. 这里,增加一个限定规则,即,ajax拉取返回的必须是一个promise对象.

function ajax(url){
    return new Promise(function(res,rej){
        send(url,function(result){
            res(result)
        })
    })
}

我们再补充一下,如果使用generator来对promise进行tricky

function runGenerator(g) {
    var it = g(), ret;
    (function iterate(val){
        ret = it.next( val );
        if (!ret.done) {
            // 检查是否已经then完成
            if ("then" in ret.value) {
                // 这一句很关键
                ret.value.then( iterate );
            }
            else {
                // 同步回调的trick
                setTimeout( function(){
                    iterate( ret.value );
                }, 0 );
            }
        }
    })();
}

OK, 这样, 我们就可以在async里面,使用同步的写法,来代表异步的操作了.

runGenerator(function* (){
    var result = yield new Promise(function(res,rej){
        setTimeout(function(){
            res('ok');
        },1000)
    });
})

由于这里要求的是使用promise, 那么,我们使用Promise.all([xx,xx]) 也是合情合理的. 这样就可以完美的解决掉--并行异步发送。

runGenerator(function* (){
    yield Promise.all([ajax('http://villainhr.com'),ajax('http://villainhr.com')])
})

对比与,使用async的结构:

(async function(){
    await Promise.all([ajax('http://villainhr.com'),ajax('http://villainhr.com')]);
})();

是不是感觉一毛一样呢? 不过在实际上操作中, async 还必须对new Promise进行兼容处理. 如果其他人直接传入一个expression, 你也必须保证他是可行的. 在babel中,讲的其实也是这样一个逻辑:

// In

async function foo() {
  await bar();
}
// Out

var _asyncToGenerator = function (fn) {
  ...
};
var foo = _asyncToGenerator(function* () {
  yield bar();
});

具体参考: babel es6 转码

工程实践 async

前面已经基本介绍如何使用 async 这里,简单介绍一下如何在工程中接入 async。这里,我们以 webpack为例,只需要额外下载 stage-3,并修改配置即可。

# 下载 stage-3
npm install babel-preset-stage-3 --save-dev

# 修改 config 配置
module: {
  loaders: [{
    test: /\.jsx?$/,
    include: [
      path.resolve(__dirname, 'src')
    ],
    loader: 'babel-loader',
    query: {
      presets: ['es2015', 'stage-3', 'react'],
    }
  }]
}

不过,上面那种是针对浏览器环境比较好的条件下,一般来说,如果针对一些低版本浏览器,还需要使用 stage-0 的配置,那么写法就应该改成。

entry: ['babel-polyfill', './test.js'],
loaders: [
      { test: /\.jsx?$/, loader: 'babel', exclude:/node_modules/,
      presets: ['es2015','stage-0', 'react'],
      }
    ]

在具体使用 async 时,会遇到各种使用 case,这里我们按照顺序,来简单的描述一下。

基础使用

在使用 await 时,需要注意,其修饰的就是一个 Promise 对象 或者 async 函数,不能修饰非异步对象。并且,使用 await 时,外部块级作用域一定需要使用 async 进行包裹。

function promiseFunc () {
  return fetch('https://api.github.com/whatever')
    .then((data) => {
      return data.status;
    });
}

async function AsyncFunc () {
  const data = await promiseFunc ();
  console.log(data); 
  return data;
}

async function wrap() {
  const data = await AsyncFunc ();
  console.log(data); 
}

reject 错误捕获

使用 async 时,捕获 Promise 中的错误写法有两种,一种是直接使用 try-catch 进行捕获,一种是直接通过 catch() 来捕获。

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另一种写法

async function myFunction() {
  await somethingThatReturnsAPromise().catch(function (err){
    console.log(err);
  });
}

并行 async

使用并行 async 时,可以直接利用 Promise.all 执行即可:

const [foo, bar] = await Promise.all([getFoo(), getBar()]);

非 async 修饰时调用 await

正常情况下,使用 await 是需要包裹在 async 这样才能利用 generator 来暂停当前块级作用域。否则的话,他会直接按照异步的模式,顺序执行。

async A(v) {
    console.log('class A');
    return await Promise.resolve();
  }

async B(foo) {
    console.log('class B');
    return await Promise.resolve();
  }

A().then(()=>{console.log("1")})
B().then(()=>{console.log("2")})

# 只会输出
A
B
1
2

如果要保证顺序执行的话,则需要使用 async 进行包裹。

(asycn function test(){
    await A();
    console.log("1")
    await B();
    console.log("2")
})()

常用技巧

检测浏览器支持 async

有时候需要了解一个浏览器是否支持 async ,然后针对不同的异步写法来做相关的兼容。这时,异步检测特性就显得极为重要。那如何快速用同步的方式在浏览器里面检测是否支持 async,可以直接通过 newFunction(xxx) 的方式来做。直接看代码吧:

function asyncDetect(){
    try {
        new Function('async function test() {}')();
      } catch (e) {
        return false;
      }
      return true;
}

如果你需要检测是否支持 generator 的话,可以直接使用:

function generatorDetect (){
    try {
        new Function('function* test() {}')();
      } catch (e) {
        return false;
      }
      return true;
}

参考

async/await 使用

async rejected 的使用

原文发布于微信公众号 - 前端小吉米(villainThr)

原文发表时间:2018-04-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Janti

Java基础巩固计划

3.26-4.1 JVM 虚拟机的内容写五篇博客 解决以下问题: 1. Java的内存模型以及GC算法 2. jvm性能调优都做了什么 3. 介绍JVM中7个区...

3747
来自专栏对角另一面

lodash源码分析之Hash缓存

在那小小的梦的暖阁,我为你收藏起整个季节的烟雨。 ——洛夫《灵河》 本文为读 lodash 源码的第四篇,后续文章会更新到这个仓库中,欢迎 star:poc...

3199
来自专栏我是攻城师

ElasticSearch嵌套模型基本操作

3785
来自专栏java一日一条

ava多线程:volatile变量、happens-before关系及内存一致性

请参考来自 Jean-philippe Bempel 的评论。他提到了一个真实因 JVM 优化导致死锁的例子。我尽可能多地写博客的原因之一是一旦自己理解错了,可...

452
来自专栏Golang语言社区

Golang同步:锁的使用案例详解

互斥锁 互斥锁是传统的并发程序对共享资源进行访问控制的主要手段。它由标准库代码包sync中的Mutex结构体类型代表。只有两个公开方法 Lock Unlock ...

3728
来自专栏牛肉圆粉不加葱

[Spark源码剖析]Task的调度与执行源码剖析

一个Spark Application分为stage级别和task级别的调度,stage级别的调度已经用[DAGScheduler划分stage]和[DAGSc...

742
来自专栏码洞

Spark通信原理之Python与JVM的交互

我们知道Spark平台是用Scala进行开发的,但是使用Spark的时候最流行的语言却不是Java和Scala,而是Python。原因当然是因为Python写代...

711
来自专栏北京马哥教育

Python自动化运维之高级函数

一、协程 1.1 协程的概念 协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程。(其实并没有说明白~) 那么...

31711
来自专栏对角另一面

lodash源码分析之Hash缓存

本文为读 lodash 源码的第四篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash

1897
来自专栏xiaoxi666的专栏

谈谈知识的融汇贯通:以“java中的迭代器失效问题”为例

参考文章 java迭代器失效 和 Collection与Iterator的remove()方法区别与ConcurrentModificationExceptio...

702

扫码关注云+社区