async, await and try catch

New Zeland

這篇想說一下asyncawait語法的一些小細節,首先從async來說吧,一般來說,async function 是在內部有需要用await等 Promise 結果的時候才使用,也由於這個特性,async function 的回傳值都會是個 Promise,意思就是你回傳非 Promise 的值,會自動被包成 Promise,所以像下面的程式:

async function wow () {
    return Promise.resolve(100);
}

wow().then(v => { console.log(v); });

就等同於:

async function wow () {
    return 100;
}

wow().then(v => { console.log(v); });

和直接回 Promise value 比起來,效能上不會有什麼顯著差異,從建議的實做方法來看就是多一個判斷。再來看看await吧,首先一樣,await一般是用來接 Promise 的,不過其實也是可以接非 Promise value 的

async function wow () {
  var r = await 1;
  console.log(1);
}

wow();
console.log(2);

所以這樣的程式碼也可以正確執行,不過 await 那邊的執行方式還是會維持非同步的(實際上應該是後面的東西都會用 Promise 包起來一次),所以這段程式碼的輸出會是先輸出2再輸出1

再來這點可能比較多人知道,就是連續的多個await不會讓這些非同步操作同時開始:

async function wow () {
  const a = await fetch('/a');
  const b = await fetch('/b');
  const c = await fetch('/c');

  return [a, b, c];
}

這樣其實三個請求會照順序執行,a有結果了才去要bb有結果了才去要c,而不是同時處理,如果要同時發出請求則還是需要用Promise.all,然後不用async了:

function wow () {
  return Promise.all([
    fetch('/a'),
    fetch('/b'),
    fetch('/c')
  ]);
}

不要await的話,也是可以先 assign 給變數的:

function wow () {
  const a = fetch('/a');
  const b = fetch('/b');
  const c = fetch('/c');

  return Promise.all([a, b, c]);
}

然後其實Promise.all是要所有的 Promise 都 fulfilled 時才會 resolve,另外一個角度來看,就是其中只要一個 rejected 的話,就不會 resolve,實際上使用起來變化有點少,而且要做忽略錯誤的fetch也有點麻煩,所以現在 TC39 還有個新的草案叫Promise.allSettled,不管是 resolve 還是 reject,只要所有參數內的 Promise 都結束了,allSettled就會 resolve,目前這草案還在 stage 1,過幾天的會議有望升到 stage 2,不過這是題外話。

最後一個想說的就是await處理 rejected Promise 的問題,如果是從 jQuery 時期就開始寫 Deferred/Promise 的人,可能會很習慣的把 Promise 的兩種狀態拿來當成值的一部份,事實上這也是jQuery.ajax的設計,如果用這種想法來寫await接值的時候,就會覺得很難處理rejected的狀態,因為要用try...catch

async function wow () {
  try {
    const a = await fetch('/a');     
  } catch (error) {
    // deal with non-ok fetch
  }
}

要這樣寫還不如用舊的.then來接看起來還漂亮一點。不過實際上,這是錯誤的理解 Promise,Promise 不是用來取得兩種狀態用的,而是用來非同步取得單一個數值用的機制,而所謂rejected的狀態,其實就是發生非預期狀況(unexpected exception)的情形,這也就是為什麼 ECMAScript 版的 Promise 是用throw Error的方式來 reject Promise。

我一直覺得用 HTTP 請求來比較這兩種設計蠻好理解的,使用 jQuery 的ajax,server 端回非 200 的 status 的話,就會被當成是錯誤,然後回傳的 Promise 就會被 reject,但是在使用 ECMAScript Promise 的fetch中,不管 server 端回應的 status code,fetch 都會 resolve,而會 reject 的情形,就只有網路有問題的時候,像是網路斷線、存取被拒絕(CORS)等完全碰不到遠端主機的情形,也就是對於一個 HTTP 請求來說,真正的非預期狀況,所以如果你有兩種狀況要處理,那應該是回傳值的一部份,後面再用if...else來做分支。

回來看await的使用,究竟應該什麼時候來用try...catch呢,我自己有一個很簡單的初步判斷條件,就是這個取值的程式碼,如果不是非同步,沒有使用await的話,你會不會用try...catch包起來,不會的話,那改成非同步操作的程式碼應該也不用try...catch。不過現實世界當然還是比較難一點,非同步的取值風險和狀況還是比較多的,例如fetch遇到網路問題會 reject,但是還是需要處理這種狀況,不用try...catch的話,怎樣寫比較好呢?我的想法是,用.then/catch處理好需要處理的情形,然後把結果包起來傳回去,所以要處理fetch的非預期狀況的話,就可以改成:

async function wow () {
  const a = await fetch('/a').catch(error => {
    return {
      ok: false,
      status: -1,
      error: error,  
    };
  });
  
  if (a.status === -1) {
    // exception error handling
  }
}

這邊我設計成有非預期狀況時,status code 為-1,並且把 error 資訊也傳回去,然後後面就可以直接拿來判斷是不是非預期狀況,當然也可以把這個處理包成一個自己的myFetch

const myFetch = (url, options) =>
  fetch(url, options)
    .catch(error => {
      ok: false,
      status: -1,
      error: error,  
    });

然後原來的程式就可以直接拿myFetch取代fetch了。

如果要通用一點的,其實有一個叫await-to-js的套件我覺得蠻不錯的,直接拿官方的範例看吧:

import to from 'await-to-js';

async function asyncTaskWithCb(cb) {
  let [err, user] = await to(UserModel.findById(1));
  if (!user) return cb('No user found');
}

它可以包裝 Promise 物件,然後不管那個 Promise 成功還是失敗,它自己都會 resolve,resolve 的值就是[error, value]這樣形式的陣列,一來符合 node 的error-first callbacks,再來就是配合 destructuring assignment 其實程式碼是蠻漂亮的。


ES Module for NPM Package

Queenstown

For English reader:https://github.com/othree/til/blob/master/js/esm-package.md

這個問題我卡蠻久了,最近才解決加上找好一些資訊的來源,目標就是要讓一個 NPM package 同時提供 CommonJS module 和 ES module 的版本,現在很多地方可以用 ES module 了,像是 Node.js 自己有經有在測試用mjs副檔名,webpack 和 rollup 也都支援 ES module 的 bundle,而且要tree shaking的功能也需要使用 ES module,用以前的 CommonJS 是不支援的,不多廢話,直接看怎樣做吧:

{
  "name": "smartypants",
  "version": "0.1.1",
  "main": "smartypants",
  "module": "smartypants.es6.js",
  "jsnext:main": "smartypants.es6.js",
  ...
}

package.json 這樣寫,然後需要提供以下三個檔案:

-rw-r--r--  1 othree  staff  21874 Jul 14 10:38 smartypants.es6.js
-rw-r--r--  1 othree  staff  24885 Jan  9 17:12 smartypants.js
-rw-r--r--  1 othree  staff  21874 Jul 14 10:38 smartypants.mjs

這段是我從smartypants.js那邊拿來的,重點在:

  1. main裡面的檔名不寫副檔名,該檔名要同時提供jsmjs兩種
  2. 多加上module這筆設定

說明一下,Node.js 現在判斷是哪種模組格式的方式是看副檔名,所以一定要mjs的檔案才會當成 ES module,然後剛好解析main檔案時的副檔名會自動補,所以就乾脆拿掉,同時提供smartypants.jssmartypants.mjs兩個檔案,其實都是main用的;再來是module這個設定和 Node.js 以及 NPM 無關,其實是rollup提出來的pkg.module,rollup 如果在解析模組實有看到這個設定,就可以把這個檔案拿來用,當時設計是這個設定 ES module,以前的 main 則是 CommonJS module,雖然是 rollup 提出的,不過 webpack 現在也支援了,範例中還有一筆jsnext:main則是比較早期用的 key。

再更進階一點,還有目標對象的問題,就是產出是瀏覽器用的還是 server 端用的,以前這問題不太常見,不過隨著 server side rendering 越來越普及,這問題就開始比較多人關注了,webpack 就有支援 bundle 的目標對象,也有支援pkg.browser設定,webpack 的issue #5673有不少討論,有興趣的可以參考看看,不過要注意的是browser似乎是第一順位,設定的時候要小心點。


Kobo Forma

Kobo Forma

最近終於入手我的第一台電子紙式的電子書了,其實嚴格說來是中了 Kobo 行銷的坑,其實我是一直有想買一台電子書很久,今年 Kobo 兩週年活動很大手筆的全站 50% off,加上還有開賣 Kobo Aura One,雖然當時已經有 Kindle 要進台灣的風聲了,不過現在中文書 Kobo 已經有很多了,加上漫畫輕小說也都有,所以最後就還是下手了,結果在等待到貨的時候,就看到有新機種的風聲,後來確定之後我就立馬取消 Aura One 的訂單,還好我當時沒買到現貨,新款就是現在入手的Kobo Forma了。

閱讀「Kobo Forma」全文

瀏覽器多樣性 Browser Diversity

前陣子大事就是微軟要放棄自家的 EdgeHTML 引擎,轉用 Chromium 專案為基礎來開發新版的 Edge Browser 了,風聲剛出來的時候,我注意到微軟官方完全沒做出回應,也沒有任何微軟員工出來講話,加上有些媒體早就發現到有 Edge 的開發成員在貢獻 Chromium,我就覺得是真的了,後來十二月六日微軟正式回應,還有一份比較長的聲明,Mozilla 也有回應,其實這件事情對於網路生態算是很大的衝擊,不過一般使用者可能沒什麼感覺,加上都沒看到中文的文章寫這件事情的影響,所以只好我來寫一下了。

首先,我講可能沒什麼公信力,所以可以直接來看一下 Google(?) 其中一集 HTTP 203 短片,標題是 Browser Monoculture,Monoculture 剛好是 Diversity 的相對,mono 是單一,單聲道或是黑白影像都是用 mono,culture 就是文化,Monoculture 的意思自然可以明白:

事實上,當微軟放棄 EdgeHTML 引擎之後,現在整個生態圈只剩下 Firefox 的 Gecko 引擎和 WebKit 家族(Safari 的 WebKit 和 Chrome 的 Blink ),而 WebKit 家族現在的市佔率已經是超過八成的獨大局面,行動裝置領域更是嚴重,如果扣除 iOS 的 Mobile Safari 則幾乎都是 Chrome 的天下了,幾乎是回到 IE 壟斷的時光,不過其實我覺得現在狀況又比那時候更險峻一點,有兩個問題:第一個是現在的 Web Platform 已經太複雜了,HTML 本身還算是單純的部分,但是各種 CSS、Web API 的推陳出新,再加上安全性、親和力、網路連線、Extensible Web 等等,到底有多少東西呢?可以參考我之前貼過的Web Platform那篇文章中Google Chrome 的 Platform Status,光這邊登記在案的,現在就有 1255 個功能,還有不少面向的東西不會列在這邊,像是效能、開發工具、WebDriver 等,其實我已經不認為現在有什麼其他第三方勢力還有辦法維護一個獨自的瀏覽器引擎了,就算是 Google 一開始也是從 WebKit,Mozilla 也是從 Gecko 來發展,微軟今天放棄繼續開發 EdgeHTML 之後,可能過一兩年就會讓它難以再次跟上標準的發展,其實微軟當年能從把 IE 重構成現在的 Edge 我覺得實在很厲害了,不過未來這種事情難度只會越來越高。

第二個是如果 Chrome 已經佔有率這麼高,它是不是可以自己開始亂搞加功能呢?就像是以前的 IE。答案其實是可以,只是現在手法已經進化了,以下舉個例子,不過先消毒,我不是指控 Google Chrome 團隊這件事是 be evil,而是假設要 be evil,這已經是可行的方法,或是換個角度,他們其實不覺得自己在 be evil,只是結果就是這樣了。

我要舉的例子是前兩週 Chrome 發表了他們支援 Background Fetch 這個新標準的消息,我第一時間反應其實是,WTF 我怎麼完全沒聽過這東西,然後我就去查查怎麼回事,然後了解到:

  1. 看介紹大概就了解這個需求確實是有的
  2. Chrome 外其他家都還沒有說要支援(根據Chrome Platform Status
  3. 標準文件的兩位編輯都是 Google 的人,主要應該是 Jake Archibald
  4. WICG那邊是去年二月也是由 Jake Archibald 提的,然後 W3C 那邊根據 blink-dev 的正向回饋就決定接收提案了

這狀況有點讓我聯想到「進化的獨裁者」,一切該有的過程其實都有,但是就都是他們的人,自己提案說有這需求,有寫好文件了,在自己家的討論區得到正向回饋,然後實做起來馬上就有市場 80% 的支援度了,這樣要不要直接算正式的網路標準了,其他家(aka Firefox)又情何以堪。事實上 Chrome 這樣衝網路標準的狀況也好一陣子了,早在 2015 年 ppk 就已經有提出對於標準發展太快的疑慮而發了一篇Stop pushing the web forward

而除了快速的發展新功能之外,還有一種狀況是擱置他們覺得不重要的 Web API 開發,然後因為已經獨大了,所以開發者就算很想要這個新的 Web API 也是無能為力,這其實也就是 HTTP 203 短片有提到的,多樣性意味著開發者有選擇的權力,而有這個力量才能讓兩邊對等。

而除了這兩個問題之外,也有人提出我之前沒想過的安全性問題,剛好就在微軟發佈消息之後沒多久就爆發出SQLite 漏洞,ZDNet 的標題提到影響到所有以 Chromium 為基礎的瀏覽器,這也是一個我之前沒想到過的問題,如果獨大的軟體有嚴重的漏洞,那一下就直接影響超多人,不過其實這次的漏洞連 Firefox 也有受影響,然後也還好不是直接可以遠端就下手的漏洞,其實佔有率高的軟體或服務都一直是駭客的目標,想必 Chromium 的 Blink 核心之後勢必會更加受到駭客關注吧。

這陣子這個圈子很多人都已經發表過看法了,像是ppkZeldman都出來發表意見,如果有人不知道這兩位是誰,趁機介紹一下,Jeffery Zeldman 是Designing with Web Standards的作者,A List Apart(A Book Apart, An Event Apart 等)和Web Standards Project的發起人,也是當年推動瀏覽器實做應該回歸網路標準的意見領袖,ppk 也是那個時期蠻活躍的,做了很多相容性測試,著有 ppk on JavaScript,當年是很棒的入門書。大部分的人其實都是針對 monoculture 論述,然後建議大家現在就開始行動,包括確保你的網站支援 Firefox、開始使用 Firefox 等等,不過 Lea Verou 有則評論則是針對那些覺得少一個瀏覽器要測試很高興的開發者,講的比較重:

至於事主之一的 Google 則就裝死當沒他的事。總之,現在雖然 Firefox 還有個 10% 左右的佔有率,光看數字還比 IE 那時候好,但是我卻覺得情勢更加險峻,很難再有新的競爭者出來,只能希望 Google don't be evil,還有 Mozilla 能夠堅持下去,真是有點想念還有五大主流瀏覽器的時候啊。


記錄組記錄記錄

COSCUP 2014 Day1 記錄組

這是篇拖稿超過一年以上的文章啊,去年就把一部份大綱騰好了,結果一直沒動筆,剛好今年開始變半退休狀態的紀錄組員,所以想說不能再拖了,才終於開始動手了,接下來就來記錄這幾年來參與社群研討會記錄組的記錄吧。

雖然大部分的研討會稱為記錄組(documentary),但是其實幾乎是只負責攝影記錄(拍照、photography),HITCON 以前倒是就很正確的稱為攝影組,這兩年又改成記錄組,反倒是 COSCUP 以前叫記錄組,今年叫攝影組了... 其實記錄這詞並沒限制在攝影記錄上,COSCUP 曾經有幾年是記錄組還有處理過錄影記錄,不過近年工作量太大,都包組長會太吃力,所以後來錄影記錄部分都還是拆出去跟直播混在一起,而除了攝影紀錄和錄影記錄外,其實還有文字紀錄,不過就我所知只有 JSDC 有官方做的,TonyQ 的堅持。總之後面文章依然稱為記錄組,實際上是主要在討論攝影紀錄的部分啦。

以下預計分為兩篇,第一篇(就是這篇)先記錄我這幾年的心路歷程,後篇記錄記錄(攝影)組現在的工作方式、流程等細節,也可以當作公開文件,第二篇還在寫,今年大概來不及寫完了,或許農曆年前有機會把主要的文字寫好吧。

閱讀「記錄組記錄記錄」全文

➡ 看看其它文章