ES6 Async/Await 完爆Promise的6个缘由

自从Node的7.6版本,已经默认支持async/await特性了。若是你尚未使用过他,或者对他的用法不太了解,这篇文章会告诉你为何这个特性“不容错过”。本文辅以大量实例,相信你能很轻松的看懂,并了解Javascript处理异步的一大杀器。javascript

文章灵感和内容借鉴了6 Reasons Why JavaScript’s Async/Await Blows Promises Away (Tutorial),英文好的同窗能够直接戳原版参考。java

初识Async/await

对于还不了解Async/await特性的同窗,下面一段是一个“速成”培训。
Async/await 是Javascript编写异步程序的新方法。以往的异步方法无外乎回调函数和Promise。可是Async/await创建于Promise之上。对于Javascript处理异步,是个老生常谈却历久弥新的话题:git

从最先的回调函数,到 Promise 对象,再到 Generator 函数,每次都有所改进,但又让人以为不完全。它们都有额外的复杂性,都须要理解抽象的底层运行机制。
异步编程的最高境界,就是根本不用关心它是否是异步。程序员

async 函数就是隧道尽头的亮光,不少人认为它是异步操做的终极解决方案。github

Async/await语法

试想一下,咱们有一个getJSON方法,该方法发送一个异步请求JSON数据,并返回一个promise对象。这个promise对象的resolve方法传递异步得到的JSON数据。具体例子的使用以下:编程

const makeRequest = () =>
    getJSON()
        .then(data => {
            console.log(data)
            return "done"
        })

makeRequest()

在使用async/await时,写法以下:数组

const makeRequest = async () => {
    console.log(await getJSON())
    return "done"
}

makeRequest()

对比两种写法,针对第二种,我须要进一步说明:promise

1)第二种写法(使用async/await),在主体函数以前使用了async关键字。在函数体内,使用了await关键字。固然await关键字只能出如今用async声明的函数体内。该函数会隐式地返回一个Promise对象,函数体内的return值,将会做为这个Promise对象resolve时的参数。
可使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操做完成,再接着执行函数体内后面的语句。服务器

2)示例中,await getJSON() 说明console.log的调用,会等到getJSON()返回的promise对象resolve以后触发。app

咱们在看一个例子增强一下理解,该例子取自阮一峰大神的《ECMAScript 6 入门》一书:

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。

Async/await究竟好在哪里?

那么,一样是处理异步操做,Async/await究竟好在哪里呢?
咱们总结出如下6点。

简约而干净Concise and clean

咱们看一下上面两处代码的代码量,就能够直观地看出使用Async/await对于代码量的节省是很明显的。对比Promise,咱们不须要书写.then,不须要新建一个匿名函数处理响应,也不须要再把数据赋值给一个咱们其实并不须要的变量。一样,咱们避免了耦合的出现。这些看似很小的优点实际上是很直观的,在下面的代码示例中,将会更加放大。

错误处理Error handling

Async/await使得处理同步+异步错误成为了现实。咱们一样使用try/catch结构,可是在promises的状况下,try/catch难以处理在JSON.parse过程当中的问题,缘由是这个错误发生在Promise内部。想要处理这种状况下的错误,咱们只能再嵌套一层try/catch,就像这样:

const makeRequest = () => {
    try {
    getJSON()
        .then(result => {
            // this parse may fail
            const data = JSON.parse(result)
            console.log(data)
        })
        // uncomment this block to handle asynchronous errors
        // .catch((err) => {
        //   console.log(err)
        // })
        } 
    catch (err) {
        console.log(err)
    }
}

可是,若是用async/await处理,一切变得简单,解析中的错误也能垂手可得的解决:

const makeRequest = async () => {
      try {
          // this parse may fail
          const data = JSON.parse(await getJSON())
          console.log(data)
      } 
      catch (err) {
          console.log(err)
      }
   }

条件判别Conditionals

想象一下这样的业务需求:咱们须要先拉取数据,而后根据获得的数据判断是否输出此数据,或者根据数据内容拉取更多的信息。以下:

const makeRequest = () => {
    return getJSON()
        .then(data => {
            if (data.needsAnotherRequest) {
                return makeAnotherRequest(data)
                        .then(moreData => {
                            console.log(moreData)
                            return moreData
                        })
            } 
            else {
                console.log(data)
                return data
            }
        })
}

这样的代码会让咱们看的头疼。这这么多层(6层)嵌套过程当中,很是容易“丢失自我”。
使用async/await,咱们就能够垂手可得的写出可读性更高的代码:

const makeRequest = async () => {
    const data = await getJSON()
    if (data.needsAnotherRequest) {
        const moreData = await makeAnotherRequest(data);
        console.log(moreData)
        return moreData
    } 
    else {
        console.log(data)
        return data    
    }
}

中间值Intermediate values

一个常常出现的场景是,咱们先调起promise1,而后根据返回值,调用promise2,以后再根据这两个Promises得值,调取promise3。使用Promise,咱们不难实现:

const makeRequest = () => {
    return promise1()
        .then(value1 => {
            // do something
            return promise2(value1)
                .then(value2 => {
                    // do something          
                    return promise3(value1, value2)
                })
        })
}

若是你难以忍受这样的代码,咱们能够优化咱们的Promise,方案是使用Promise.all来避免很深的嵌套。
就像这样:

const makeRequest = () => {
    return promise1()
        .then(value1 => {
            // do something
            return Promise.all([value1, promise2(value1)])
        })
        .then(([value1, value2]) => {
            // do something          
            return promise3(value1, value2)
        })
}

Promise.all这个方法牺牲了语义性,可是获得了更好的可读性。
可是其实,把value1 & value2一块儿放到一个数组中,是很“蛋疼”的,某种意义上也是多余的。

一样的场景,使用async/await会很是简单:

const makeRequest = async () => {
    const value1 = await promise1()
    const value2 = await promise2(value1)
    return promise3(value1, value2)
}

错误堆栈信息Error stacks

想象一下咱们链式调用了不少promises,一级接一级。紧接着,这条promises链中某处出错,以下:

const makeRequest = () => {
    return callAPromise()
        .then(() => callAPromise())
        .then(() => callAPromise())
        .then(() => callAPromise())
        .then(() => callAPromise())
        .then(() => {
            throw new Error("oops");
        })
}

makeRequest()
    .catch(err => {
        console.log(err);
        // output
        // Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
    })

此链条的错误堆栈信息并没用线索指示错误到底出如今哪里。更糟糕的事,他还会误导开发者:错误信息中惟一出现的函数名称其实根本就是无辜的。
咱们再看一下async/await的展示:

const makeRequest = async () => {
    await callAPromise()
    await callAPromise()
    await callAPromise()
    await callAPromise()
    await callAPromise()
    throw new Error("oops");
}

makeRequest()
    .catch(err => {
        console.log(err);
        // output
        // Error: oops at makeRequest (index.js:7:9)
    })

也许这样的对比,对于在本地开发阶段区别不是很大。可是想象一下在服务器端,线上代码的错误日志状况下,将会变得很是有意义。你必定会以为上面这样的错误信息,比“错误出自一个then的then的then。。。”有用的多。

调试Debugging

最后一点,可是也是很重要的一点,使用async/await来debug会变得很是简单。
在一个返回表达式的箭头函数中,咱们不能设置断点,这就会形成下面的局面:

const makeRequest = () => {
    return callAPromise()
        .then(()=>callAPromise())
        .then(()=>callAPromise())
        .then(()=>callAPromise())
        .then(()=>callAPromise())
}

咱们没法在每一行设置断点。可是使用async/await时:

const makeRequest = async () => {
    await callAPromise()
    await callAPromise()
    await callAPromise()
    await callAPromise()
}

总结

Async/await是近些年来JavaScript最具革命性的新特性之一。他让读者意识到使用Promise存在的一些问题,并提供了自身来代替Promise的方案。
固然,对这个新特性也有必定的担忧,体如今:
他使得异步代码变的再也不明显,咱们好不容易已经学会并习惯了使用回调函数或者.then来处理异步。新的特性固然须要时间成本去学习和体会;
退回来讲,熟悉C#语言的程序员必定会懂得这些学习成本是彻底值得的。

Happy Coding!

PS: 做者Github仓库,欢迎经过代码各类形式交流。

相关文章
相关标签/搜索