ES7中,async/await
语法使异步promise
的协调变得很简单。若是你须要以特定顺序异步获取来自多个数据库或API的数据,可使用杂乱的promise或回调函数。async/await
使咱们能够更简便地处理这种逻辑,代码的可读性和可维护性也更好。java
在该教程中,咱们用图表和一些简单的例子来解释async/await
的语法和语义。
开始讲解以前,咱们先对promise
进行一个简单的概述,若是你对promise已经很熟悉了,能够跳过该部份内容。node
在js中,promise表示抽象的非阻塞异步执行。js中的promise与Java中的 Future
或C#中的Task
很类似。数据库
promise一般用于网络和I/O操做-例如,读取文件,发起HTTP请求。为了避免阻塞当前执行线程,咱们建立一个异步promise,使用then方法绑定一个回调函数,该回调函数会在promise完成后触发。回调函数自己也能够返回一个promise,因此promise能够高效的链式调用。编程
简单起见,全部的例子中咱们都假定request-promise
库已经安装和加载完成了,以下所示:promise
var rp = require('request-promise');
如今咱们能够像这样发起一个简单的HTTP GET请求,该方法返回一个promise:网络
const promise = rp('http://example.com/')
接下来,看一个例子:多线程
console.log('Starting Execution'); const promise = rp('http://example.com/'); promise.then(result => console.log(result)); console.log("Can't know if promise has finished yet...");
在第三行,咱们建立了一个promise
,而后咱们在第四行中为其绑定了一个回调函数。因为promise
是异步执行的
,因此执行到第六行时,咱们不肯定promise有没有完成。屡次运行上面的代码,获得的结果可能每次都不同。更通俗地讲,promise后面的代码和promise是并行运行的。并发
在promise完成以前,没有办法中断当前的操做序列。这与Java中的 Future.get
是不一样的,Future.get
容许咱们中断当前的线程直到Future
完成。js中,咱们不会轻易地等待promise执行完成。在promise完成以后安排代码的惟一方式是经过then
方法绑定回调函数。异步
下图描述了该示例的计算过程:async
then
方法中绑定的回调函数只有当promise成功的时候才会调用。若是promise失败的话(例如,因为网络错误),回调不会执行。为了处理失败的promise,须要经过catch
绑定另外一个回调函数。
rp('http://example.com/'). then(() => console.log('Success')). catch(e => console.log(`Failed: ${e}`))
最后,为了测试一下效果,咱们经过Promise.resolve
和Promise.reject
简单地生成成功和失败的promise:
const success = Promise.resolve('Resolved'); // Will print "Successful result: Resolved" success. then(result => console.log(`Successful result: ${result}`)). catch(e => console.log(`Failed with: ${e}`)) const fail = Promise.reject('Err'); // Will print "Failed with: Err" fail. then(result => console.log(`Successful result: ${result}`)). catch(e => console.log(`Failed with: ${e}`))
有关promise更详细的教程,查看这篇文章
单个promise是很简单的。但是,咱们编写复杂的异步逻辑时,可能须要组合使用多个promise来处理。大量的then
语句和匿名回调函数很容易让代码变得不可维护。
例如,咱们要编写一个以下功能的代码:
发起一个HTTP请求,等待完成后,打印出结果
而后发起两个并行的HTTP请求;
后两个请求都完成后,打印出他们的结果。
下面的代码片断演示了上述功能的实现:
// Make the first call const call1Promise = rp('http://example.com/'); call1Promise.then(result1 => { // Executes after the first request has finished console.log(result1); const call2Promise = rp('http://example.com/'); const call3Promise = rp('http://example.com/'); return Promise.all([call2Promise, call3Promise]); }).then(arr => { // Executes after both promises have finished console.log(arr[0]); console.log(arr[1]); })
首先发起第一个HTTP请求,当该请求完成后,调用它的回调函数(1-3行)。在回调函数中,咱们又相继发起两个HTTP请求生成了两个promise。这两个promise并行运行;当他们都执行完后,咱们还须要为其绑定一个回调函数。所以,咱们用promise.all
将这两个promise组合成一个promise, 只有当他们都完成后,这个promise才会完成。因为第一个回调函数的结果是promise,所以咱们链式地调用另外一个then方法和回调函数输出最终结果。
下图描述了这个执行过程:
对于这么简单的例子,咱们就用了两个then
回调和promise.all
来同步并行的promise。试想若是咱们执行更多的异步操做或者增长错误处理函数呢?这种方式很容易让代码变成一堆杂乱的then
、promise.all
和回调函数。
async 函数提供了一种简洁的方式来定义一个返回promise的函数。
例如,下面两种定义是等价的:
function f() { return Promise.resolve('TEST'); } // asyncF is equivalent to f! async function asyncF() { return 'TEST'; }
类似地,在异步函数抛出异常与返回一个reject promise对象的函数等价:
function f() { return Promise.reject('Error'); } // asyncF is equivalent to f! async function asyncF() { throw 'Error'; }
咱们不能同步等待promise的完成。只能经过then
方法传入一个回调函数。咱们鼓励非阻塞编程,所以同步等待promise是不容许的。不然,开发者会产生编写同步脚本的想法,毕竟同步编程要简单的多。
可是,为了同步promise咱们须要容许他们等待彼此的完成。换句话说,若是操做是异步的(也就是说包裹在promise中),它应该能够等待其余异步操做的完成。可是,js解析器怎么知道操做是否跑在promise中?
答案是async
关键字。每一个async
函数返回一个promise。所以,js解析器知道全部的操做都位于async
函数中,并将全部的代码包裹在promise中异步地执行。因此,async
函数,容许操做等待其余promise的完成。
说一下await
关键字。它只能用在async
函数中,容许咱们同步等待promise的完成。若是在async
函数外边使用promise,咱们仍然须要使用then
回调函数。
async function f(){ // response will evaluate as the resolved value of the promise const response = await rp('http://example.com/'); console.log(response); } // We can't use await outside of async function. // We need to use then callbacks .... f().then(() => console.log('Finished'));
如今咱们看一下前面的那个例子如何用async/await
进行改写:
/ Encapsulate the solution in an async function async function solution() { // Wait for the first HTTP call and print the result console.log(await rp('http://example.com/')); // Spawn the HTTP calls without waiting for them - run them concurrently const call2Promise = rp('http://example.com/'); // Does not wait! const call3Promise = rp('http://example.com/'); // Does not wait! // After they are both spawn - wait for both of them const response2 = await call2Promise; const response3 = await call3Promise; console.log(response2); console.log(response3); } // Call the async function solution().then(() => console.log('Finished'));
以上代码,咱们的解决方案就封装在了async
函数中。咱们能够直接await
promise的执行,省掉了then
回调函数。最后,咱们只须要调用async
函数。它封装了调用其余promise的逻辑,并返回一个promise。
实际上在上面的例子中,promise是并行触发的。本例中也同样(7-8行)。注意第12-13行咱们使用了await
阻塞主线程,等待全部的promise执行完成。后面,咱们看到promise都完成了,和前面的例子相似(promise.all(...).then(...))。
其执行流程与前例的流程是相等的。可是,代码变得更具可读性和简洁。
底层实现上,await/async实际上转换成了promise,换句话说,await/async
是promise的语法糖。每次咱们使用await
时,js解析器会生成一个promise,并将async
函数中的剩余代码放到then回调中去执行。
思考下面的例子:
async function f() { console.log('Starting F'); const result = await rp('http://example.com/'); console.log(result); }
下面描述函数f的基本计算过程。因为f是异步的,它会与调用方并行执行:
函数f开始执行,遇到await
后生成一个promise。此时,函数的其他部分被封装在回调中,并在promise完成后执行。
前面的大部分例子中,咱们都是假设promise成功完成了。所以,等待promise返回一个值。若是咱们等待的promise失败了,在async
函数中会致使一个异常。咱们可使用标准的try/catch
来捕获和处理它。
async function f() { try { const promiseResult = await Promise.reject('Error'); } catch (e){ console.log(e); } }
若是async
函数没有处理异常,不论是promise reject了,仍是产生了其余bug,它都会返回一个rejected的promise对象。
async function f() { // Throws an exception const promiseResult = await Promise.reject('Error'); } // Will print "Error" f(). then(() => console.log('Success')). catch(err => console.log(err)) async function g() { throw "Error"; } // Will print "Error" g(). then(() => console.log('Success')). catch(err => console.log(err))
这给咱们提供了一种简便的方法,经过已知的异常处理机制来处理被rejected的promise。
async/await
在语言结构上是对promise的补充。可是,async/await
并不能取代纯promise的需求。例如,在正常函数和全局做用域咱们不能使用await
,因此须要使用普通的promise:
async function fAsync() { // actual return value is Promise.resolve(5) return 5; } // can't call "await fAsync()". Need to use then/catch fAsync().then(r => console.log(`result is ${r}`));
我一般会将异步逻辑封装到一个或者少数几个async
函数中,而后在非异步代码中调用async函数。这样我能够最小化下降书写then
/catch
的数量。
学者们指出,并发性和并行性是有区别的。并发性是指将独立的进程(通常意义上的进程)组合在一块儿,而并行其实是同时执行多个进程。并发性是关于应用程序设计和结构的,而并行性是关于实际执行的。
咱们以一个多线程应用程序为例。应用程序分离到线程定义了它的并发模型。这些线程在可用内核上的映射定义了它的级别或并行性。并发系统能够在单个处理器上高效运行,在这种状况下,它不是并行的。
就此而言,promise容许咱们将一个程序分解为并行的并发模块,也能够不并行运行。实际的JavaScript执行是否并行取决于实现。例如,Node Js是单线程的,若是一个promise是CPU绑定的,你就不会看到太多的并行性。然而,若是你经过像Nashorn这样的东西把你的代码编译成java字节码,理论上你可能可以在不一样核心上映射CPU绑定的promise,而且实现并行性。所以,在我看来,promise(不管是普通的或经过await/async)构成了JavaScript应用程序的并发模型。