JavaScript ES7中的 async/await
语法使得异步Promise变得更加容易。 若是您须要以某种顺序从多个数据库或API异步获取数据,则可使用promise和回调构成的面条式的代码。 async/await
构造容许咱们更简洁地表达这种逻辑且代码更易读和可维护。数据库
本教程将使用图表和简单示例来解释JavaScriptasync/await
语法和语义。编程
在咱们开始以前,让咱们从一个Promise
的简要概述开始。 若是您已经了解了JSPromise
,请随时跳过本节。promise
在JavaScript中,Promises
表明非阻塞异步执行的抽象。 若是了解其余语言的话,JSPromise与Java的Future
或C#的Task
相似。微信
Promises
一般用于网络和I/O操做 - 例如从文件读取或发出HTTP请求。 若是不须要阻塞当前的“线程”执行,咱们能够产生一个异步Promises
,并使用then方法来传入一个回调函数,它在promise完成时将被触发。 回调函数自己能够返回Promise
,所以咱们能够链式调用Promise
。网络
为了简单起见,在全部示例中,咱们假设request-promise
已经安装并能够像下面这样子加载:并发
var rp = require('request-promise');
如今咱们能够作一个简单的HTTP GET请求,返回一个Promise
异步
const promise = rp('http://example.com/')
如今,咱们来看一个例子:async
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...");
咱们在第3行产生了一个新的Promise
,而后在第4行附加一个回调函数。Promise
是异步的,因此当咱们到达第6行时,咱们不知道Promise
是否已经完成。 若是咱们屡次运行代码,咱们可能会每次获得不一样的结果。 更确切地说,任何承诺以后的代码都是与Promise
同时运行的。ide
在Promise
完成以前,咱们没有任何合理的理由阻止当前的操做顺序。 这与Java的Future.get
不一样,它容许咱们阻止当前线程,直到未来完成。 在JavaScript中,咱们不能等待Promise
完成。 在Promise
完成以后执行代码的惟一方法是经过then
方法传入回调函数。函数
下图描绘了该示例的计算过程:
Promise
的计算过程。 调用“线程”不能等待Promise
。 在Promise
以后执行代码的惟一方法是经过then
方法指定回调函数。
只有当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
结束。 编写这些Promise
和匿名回调能够很容易失去对代码的控制。
例如,假设咱们须要编写一个程序:
如下代码段演示了如何完成此操做:
// 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/'); const combinedPromise = Promise.all([call2Promise, call3Promise]); combinedPromise.then(arr => { // Executes after both promises have finished console.log(arr[0]); console.log(arr[1]); }) })
咱们首先发起了第一个HTTP请求,并在其完成时运行回调函数(第1-3行)。 在回调中,咱们为后续的HTTP请求产生了两个Promise
(第8-9行)。 这两个Promise
同时运行,咱们须要安排一个回调,在它们都完成时调用。 所以,咱们须要经过Promise.all
(第11行)将它们组合成一个单一的Promise
,当它们完成时,它们就能够正确调用。 而后咱们传入了另外一个打印结果的回调(第14-15行)。
下图描述了计算流程:
对于这样一个简单的例子,咱们最终获得了2个嵌套的回调函数,而且必须使用Promise.all来同步并发Promise
。 若是咱们不得再也不运行一些异步操做或添加错误处理怎么办? 这种方法能够很容易地改写成用Promise.all
和多个then
链接起来的链式面条代码。
咱们能够重写上面的例子来使用“promise chaining”,以下所示:
// 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]); })
这样可读性更高一些,尽管咱们仍然须要连接两个回调函数并使用Promise.all
。
Async函数是返回Promise
函数的简写。
例如,如下定义是等价的:
function f() { return Promise.resolve('TEST'); } // asyncF is equivalent to f! async function asyncF() { return 'TEST'; }
相似地,抛出异常的Async函数等效于返回reject Promise
的函数:
function f() { return Promise.reject('Error'); } // asyncF is equivalent to f! async function asyncF() { throw 'Error'; }
当咱们产生承诺时,咱们没法同步等待完成。 咱们只能经过一个回调。 不容许等待承诺鼓励开发非阻塞代码。 不然,开发人员将被诱惑执行封锁操做,由于它比使用承诺和回调更容易。
当咱们建立Promise
时,咱们没法同步等待完成。 咱们只能经过一个回调。 不容许等待Promise
,鼓励开发非阻塞代码。 不然,开发人员将更容易使用锁定当前线程的操做,由于它比使用Promise
和回调更容易。
然而,为了同步Promise
,咱们须要容许他们相互等待。 换句话说,若是操做是异步的(即封装在Promise
中),则应该可以等待另外一个异步操做完成。 可是JavaScript解释器如何知道一个操做是否在Promise
中运行?
答案是在async
关键字。 每一个async
函数都返回一个Promise
。 所以,JavaScript解释器知道async
函数中的全部操做都将被封装在Promise
中并异步运行。 因此可让他们等待其余的Promise
完成以后再继续执行。
当咱们使用await关键字。 它只能用于async
功能,并容许咱们同步等待Promise
。 若是咱们在async
函数以外使用Promise
,咱们仍然须要使用回调函数:
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'));
如今,咱们来看看咱们如何解决上一节的问题:
// 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
函数中。 这使咱们可以直接等待Promise
,从而避免了回调的须要。 最后,咱们调用async
函数,该函数只是产生一个封装了调用其余Promise
的逻辑的Promise
。
事实上,在第一个例子中(没有async/await
),这些Promise
将会并行开始。 在这种状况下,咱们作一样的(7-8行)。 请注意,直到第11-12行,当咱们使用了await
,直到两个Promise
都已经完成为止。 以后,咱们知道这两个Promise
都已经完成了(相似于前面的例子中使用Promise.all
(...)而后(...))。
实际计算过程等同于上一节所述的过程。 然而,代码更加可读和直观。
在引导下,async/await
实际上转化为Promise
,而后回调。 换句话说,它是使用Promise
的语法糖。 每次咱们等待,解释器产生一个Promise
,并将其他的操做从异步功能放在一个回调。
咱们来考虑下面的例子:
async function f() { console.log('Starting F'); const result = await rp('http://example.com/'); console.log(result); }
f函数的基础计算过程以下所示。 因为f是异步的,它也将与其调用者并行运行
函数f启动并产生Promise
。 在那一刻,函数的其他部分被封装在一个回调函数中,而且在Promise
完成以后计划执行。
在前面的大多数例子中,咱们假设Promise
成功执行了。 所以,等待Promise
返回值。 若是咱们等待失败的Promise
,这将致使异步功能中的异常。 咱们可使用标准的try/catch
来处理它:
async function f() { try { const promiseResult = await Promise.reject('Error'); } catch (e){ console.log(e); } }
若是async
函数不处理异常,不管是由拒绝Promise
仍是其余错误引发的,都将返回被拒绝的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))
这经过已知的异常处理机制使咱们方便地处理被拒绝的Promise
。
Async/await
是一种对Promise
的语言上的补充。 它容许咱们以较少的样板来使用Promise
。 可是,Async/await
不能取代纯粹Promise
的须要。 例如,若是咱们从正常函数或全局范围调用Async
函数,咱们将没法使用await
,并将诉诸于vanillaPromise
:
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}`));
我一般会尝试将大多数异步逻辑封装在一个或几个异步函数中,这是从非异步代码中调用的。 这最大限度地减小了我须要编写的try/catch
回调的数量。
Async/await
结构是更符合Promise
的语法糖。 每一个Async/await
结构能够用简单的Promise
重写。 因此,这是一个风格和简洁的问题。
关注个人微信公众号,更多优质文章定时推送