教育行業(yè)A股IPO第一股(股票代碼 003032)

全國咨詢/投訴熱線:400-618-4000

精講js異步

更新時(shí)間:2019年01月10日15時(shí)19分 來源:傳智播客 瀏覽次數(shù):

構(gòu)建一個(gè)應(yīng)用程序總是會(huì)面對異步調(diào)用,不論是在 Web 前端界面,還是 Node.js 服務(wù)端都是如此,JavaScript 里面處理異步調(diào)用一直是非常惡心的一件事情。以前只能通過回調(diào)函數(shù),后來漸漸又演化出來很多方案,最后 Promise 以簡單、易用、兼容性好取勝,但是仍然有非常多的問題。其實(shí) JavaScript 一直想在語言層面徹底解決這個(gè)問題,在 ES6 中就已經(jīng)支持原生的 Promise,還引入了 Generator 函數(shù),終于在 ES7 中決定支持 async 和 await。
基本語法
async/await 究竟是怎么解決異步調(diào)用的寫法呢?簡單來說,就是將異步操作用同步的寫法來寫。先來看下最基本的語法(ES7 代碼片段):
[JavaScript] 純文本查看 復(fù)制代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
const f = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(123);
    }, 2000);
  });
};
 
const testAsync = async () => {
  const t = await f();
  console.log(t);
};
 
testAsync();
首先定義了一個(gè)函數(shù) f,這個(gè)函數(shù)返回一個(gè) Promise,并且會(huì)延時(shí) 2 秒,resolve 并且傳入值 123。testAsync 函數(shù)在定義時(shí)使用了關(guān)鍵字 async,然后函數(shù)體中配合使用了 await,最后執(zhí)行 testAsync。整個(gè)程序會(huì)在 2 秒后輸出 123,也就是說 testAsync 中常量 t 取得了 f 中 resolve 的值,并且通過 await 阻塞了后面代碼的執(zhí)行,直到 f 這個(gè)異步函數(shù)執(zhí)行完。
對比 Promise
僅僅是一個(gè)簡單的調(diào)用,就已經(jīng)能夠看出來 async/await 的強(qiáng)大,寫碼時(shí)可以非常優(yōu)雅地處理異步函數(shù),徹底告別回調(diào)惡夢和無數(shù)的 then 方法。我們再來看下與 Promise 的對比,同樣的代碼,如果完全使用 Promise 會(huì)有什么問題呢?
[JavaScript] 純文本查看 復(fù)制代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
const f = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(123);
    }, 2000);
  });
};
 
const testAsync = () => {
  f().then((t) => {
    console.log(t);
  });
};
 
testAsync();
從代碼片段中不難看出 Promise 沒有解決好的事情,比如要有很多的 then 方法,整塊代碼會(huì)充滿 Promise 的方法,而不是業(yè)務(wù)邏輯本身,而且每一個(gè) then 方法內(nèi)部是一個(gè)獨(dú)立的作用域,要是想共享數(shù)據(jù),就要將部分?jǐn)?shù)據(jù)暴露在最外層,在 then 內(nèi)部賦值一次。雖然如此,Promise 對于異步操作的封裝還是非常不錯(cuò)的,所以 async/await 是基于 Promise 的,await 后面是要接收一個(gè) Promise 實(shí)例。

異常處理
通過使用 async/await,我們就可以配合 try/catch 來捕獲異步操作過程中的問題,包括 Promise 中 reject 的數(shù)據(jù)。
[JavaScript] 純文本查看 復(fù)制代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
const f = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(234);
    }, 2000);
  });
};
const testAsync = async () => {
  try {
    const t = await f();
    console.log(t);
  } catch (err) {
    console.log(err);
  }
};
testAsync();
代碼片段中將 f 方法中的 resolve 改為 reject,在 testAsync 中,通過 catch 可以捕獲到 reject 的數(shù)據(jù),輸出 err 的值為 234。try/catch 使用時(shí)也要注意范圍和層級。如果 try 范圍內(nèi)包含多個(gè) await,那么catch 會(huì)返回第一個(gè) reject 的值或錯(cuò)誤。
[JavaScript] 純文本查看 復(fù)制代碼
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const f1 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(111);
    }, 2000);
  });
};
const f2 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(222);
    }, 3000);
  });
};
const testAsync = async () => {
  try {
    const t1 = await f1();
    console.log(t1);
    const t2 = await f2();
    console.log(t2);
  } catch (err) {
    console.log(err);
  }
};
testAsync();
如代碼片段所示,testAsync 函數(shù)體中 try 有兩個(gè) await 函數(shù),而且都分別 reject,那么 catch 中僅會(huì)觸發(fā) f1 的 reject,輸出的 err 值是 111。
開始使用
無論是 Web 前端還是 Node.js 服務(wù)端,都可以通過預(yù)編譯的手段實(shí)現(xiàn)使用 ES6 和 ES7 來寫代碼,目前頗為流行的方案是通過 [color=var(--link-color)]Babel 將使用 ES7、ES6 寫的代碼編譯為 E6 或 ES5 的代碼來執(zhí)行。
Node.js 服務(wù)端配置
服務(wù)端使用 Babel,很簡單的方式是通過 require hook。
首先安裝 Babel:
$ npm install babel-core --save
安裝 async/await 支持:
$ npm install babel-preset-stage-3 --save
在服務(wù)端代碼的根目錄中配置 .babelrc 文件,內(nèi)容為:
{  "presets": ["stage-3"]}
在頂層代碼文件(server.js 或 app.js 等)中引入 Babel 模塊:
require("babel-core/register");
在這句后面引入的模塊,都將會(huì)自動(dòng)通過 babel 編譯,但當(dāng)前文件不會(huì)被 babel 編譯。另外,需要注意 Node.js 的版本,如果是 4.0 以上的版本則默認(rèn)支持絕大部分 ES6,可以直接啟動(dòng)。但是如果是 0.12 左右的版本,就需要通過 node -harmony 來啟動(dòng)才能夠支持。因?yàn)?stage-3 模式,Babel 不會(huì)編譯基本的 ES6 代碼,環(huán)境既然支持又何必要編譯為 ES5?這樣做也是為了提高性能和編譯效率。
配置 Web 前端構(gòu)建
可以通過增加 Gulp 的預(yù)編譯 task 來支持。
首先安裝 gulp-babel 插件:
$ npm install gulp-babel --save-dev
然后編寫配置:

[JavaScript] 純文本查看 復(fù)制代碼
1
2
3
4
5
6
7
8
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 結(jié)合 Webpack 或 Browserify 使用。
要注意的是,雖然官方也有純?yōu)g覽器版本的 Babel.js,但是瀏覽器限制非常多,而且對客戶端性能影響也較大,不推薦使用。


作者:傳智播客前端與移動(dòng)開發(fā)培訓(xùn)學(xué)院

首發(fā): http://web.itcast.cn

0 分享到:
和我們在線交談!