構建一個應用程序老是會面對異步調用,不管是在 Web 前端界面,仍是 Node.js 服務端都是如此,JavaScript 裏面處理異步調用一直是很是噁心的一件事情。之前只能經過回調函數,後來漸漸又演化出來不少方案,最後 Promise 以簡單、易用、兼容性好取勝,可是仍然有很是多的問題。其實 JavaScript 一直想在語言層面完全解決這個問題,在 ES6 中就已經支持原生的 Promise,還引入了 Generator 函數,終於在 ES7 中決定支持 async 和 await。前端
async/await 到底是怎麼解決異步調用的寫法呢?簡單來講,就是將異步操做用同步的寫法來寫。先來看下最基本的語法(ES7 代碼片斷):node
const f = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(123); }, 2000); }); }; const testAsync = async () => { const t = await f(); console.log(t); }; testAsync();
首先定義了一個函數 f
,這個函數返回一個 Promise,而且會延時 2 秒,resolve
而且傳入值 123。testAsync
函數在定義時使用了關鍵字 async
,而後函數體中配合使用了 await
,最後執行 testAsync
。整個程序會在 2 秒後輸出 123,也就是說 testAsync
中常量 t
取得了 f
中 resolve
的值,而且經過 await
阻塞了後面代碼的執行,直到 f
這個異步函數執行完。git
僅僅是一個簡單的調用,就已經可以看出來 async/await 的強大,寫碼時能夠很是優雅地處理異步函數,完全告別回調惡夢和無數的 then
方法。咱們再來看下與 Promise 的對比,一樣的代碼,若是徹底使用 Promise 會有什麼問題呢? github
const f = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(123); }, 2000); }); }; const testAsync = () => { f().then((t) => { console.log(t); }); }; testAsync();
從代碼片斷中不難看出 Promise 沒有解決好的事情,好比要有不少的 then
方法,整塊代碼會充滿 Promise 的方法,而不是業務邏輯自己,並且每個 then
方法內部是一個獨立的做用域,要是想共享數據,就要將部分數據暴露在最外層,在 then
內部賦值一次。雖然如此,Promise 對於異步操做的封裝仍是很是不錯的,因此 async/await
是基於 Promise 的,await
後面是要接收一個 Promise 實例。npm
RxJS 也是很是有意思的東西,用來處理異步操做,它更能處理基於流的數據操做。舉個例子,好比在 Angular2 中 http 請求返回的就是一個 RxJS 構造的 Observable Object,咱們就能夠這樣作:gulp
$http.get(url) .map(function(value) { return value + 1; }) .filter(function(value) { return value !== null; }) .forEach(function(value) { console.log(value); }) .subscribe(function(value) { console.log('do something.'); }, function(err) { console.log(err); });
若是是 ES6 代碼能夠進一步簡潔:瀏覽器
$http.get(url) .map(value => value + 1) .filter(value => value !== null) .forEach(value => console.log(value)) .subscribe((value) => { console.log('do something.'); }, (err) => { console.log(err); });
能夠看出 RxJS 對於這類數據能夠作一種相似流式的處理,也是很是優雅,並且 RxJS 強大之處在於你還能夠對數據作取消、監聽、節流等等的操做,這裏不一一舉例了,感興趣的話能夠去看下 RxJS 的 API。服務器
這裏要說明一下的就是 RxJS 和 async/await 一塊兒用也是能夠的,Observable Object 中有 toPromise
方法,能夠返回一個 Promise Object,一樣能夠結合 await
使用。固然你也能夠只使用 async/await 配合 underscore 或者其餘庫,也能實現很優雅的效果。總之,RxJS 與 async/await 不衝突。babel
經過使用 async/await,咱們就能夠配合 try/catch 來捕獲異步操做過程當中的問題,包括 Promise 中 reject 的數據。 app
const f = () => { return new Promise((resolve, reject) => { setTimeout(() => { reject(234); }, 2000); }); }; const testAsync = () => { try { const t = await f(); console.log(t); } catch (err) { console.log(err); } }; testAsync();
代碼片斷中將 f
方法中的 resolve
改成 reject
,在 testAsync
中,經過 catch
能夠捕獲到 reject
的數據,輸出 err 的值爲 234。try/catch
使用時也要注意範圍和層級。若是 try
範圍內包含多個 await
,那麼 catch
會返回第一個 reject
的值或錯誤。
const f1 = () => { return new Promise((resolve, reject) => { setTimeout(() => { reject(111); }, 2000); }); }; const f2 = () => { return new Promise((resolve, reject) => { setTimeout(() => { reject(222); }, 3000); }); }; const testAsync = () => { try { const t1 = await f1(); console.log(t1); const t2 = await f2(); console.log(t2); } catch (err) { console.log(err); } }; testAsync();
如代碼片斷所示,testAsync
函數體中 try
有兩個 await
函數,並且都分別 reject
,那麼 catch
中僅會觸發 f1
的 reject
,輸出的 err 值是 111。
不管是 Web 前端仍是 Node.js 服務端,均可以經過預編譯的手段實現使用 ES6 和 ES7 來寫代碼,目前最流行的方案是經過 Babel 將使用 ES七、ES6 寫的代碼編譯爲 E6 或 ES5 的代碼來執行。
服務端使用 Babel,最簡單的方式是經過 require
hook。
首先安裝 Babel:
$ npm install babel-core --save
安裝 async/await 支持:
$ npm install babel-preset-stage-3 --save
在服務端代碼的根目錄中配置 .babelrc 文件,內容爲:
{ "presets": ["stage-3"] }
在頂層代碼文件(server.js 或 app.js 等)中引入 Babel 模塊:
require("babel-core/register");
在這句後面引入的模塊,都將會自動經過 babel 編譯,但當前文件不會被 babel 編譯。另外,須要注意 Node.js 的版本,若是是 4.0 以上的版本則默認支持絕大部分 ES6,能夠直接啓動。可是若是是 0.12 左右的版本,就須要經過 node —harmory
來啓動纔可以支持。由於 stage-3 模式,Babel 不會編譯基本的 ES6 代碼,環境既然支持又何須要編譯爲 ES5?這樣作也是爲了提升性能和編譯效率。
能夠經過增長 Gulp 的預編譯 task 來支持。
首先安裝 gulp-babel 插件:
$ npm install gulp-babel --save-dev
而後編寫配置:
var gulp = require('gulp'); var babel = require('gulp-babel'); gulp.task('babel', function() { return gulp.src('src/app.js') .pipe(babel()) .pipe(gulp.dest('dist')); });
除了 Gulp-babel 插件,也可使用官方的 Babel-loader 結合 Webpack 或 Browserify 使用。
要注意的是,雖然官方也有純瀏覽器版本的 Babel.js,可是瀏覽器限制很是多,並且對客戶端性能影響也較大,不推薦使用。
LeanEngine(雲引擎)是 LeanCloud 推出的服務器端運行環境,支持 Node.js 和 Python 環境,功能強大並且目前免費,結合 LeanCloud JavaScript SDK,使本來複雜的開發工做變得簡單高效。目前也支持 Redis 和海外節點,輕鬆知足你的業務需求。
LeanCloud 使用自已的服務編寫出了不少的應用和 Web 產品。爲了方便各位開發者基於 LeanEngine 來開發應用,LeanCloud 整理了目前開發 Web 端產品的技術棧,並結合 LeanEngine 特色,推出了一套完整實用的技術解決方案:LeanEngine-Full-Stack,它已經配置了 Babel,能夠在 LeanEngine 中結合 JavaScript SDK 使用 async/await 處理異步操做。因此,還等什麼?快來下載編寫新項目吧。
Enjoy!
LeanCloud 但願可以經過構建最簡單易用的技術產品,幫助各位開發者和創業者加速產品開發,儘量地節約資源成本、時間成本和機會成本,但願本文可以幫助到你。有什麼問題,能夠在 LeanEngine-Full-Stack @GitHub 倉庫 中提交 issue,或者直接去 LeanCloud 社區 提問。