本文開始
Promisification(Promise 化) 是代表將一個 function 轉換成可以接受 callback function 並且這個 callback function 是會回傳 Promise 的。
會需要這樣將原本是 callback 機制做回傳的 function 或外部函式庫轉換成回傳 Promise 物件的機制是因為 Promise 操作起來比 callback function 更方便。
以這一系列的教學文章中的提到過的 loadScript 為例
1 2 3 4 5 6 7 8 9
| function loadScript(src, callback) { const script = document.createElement('script'); script.src = src;
script.onload = callback(null, script); script.onerror = callback(new Error(`Script load error for ${src}`));
document.head.append(script); }
|
上面這個範例撰寫的形式就是都用 callback function 來做處理。
以下就把上面的範例 Promise 化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function loadScript(src, callback) { let script = document.createElement('script'); script.src = src;
script.onload = () => callback(null, script); script.onerror = () => callback(new Error(`Script load error for ${src}`), null);
document.head.append(script); }
let loadScriptPromise = function(src) { return new Promise((resolve, reject) => { loadScript(src, (err, script) => { if (err) reject(err); else resolve(script); }); }); };
loadScriptPromise('https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js') .then(res => console.log(res)) .catch(error => console.log(error))
|
可以看到上面範例的改動部分,就是把原本的傳入 loadScript 的 callback function 改成回傳 promise 的 callback function,也就是以下這一段程式碼
1 2 3 4
| (err, script) => { if(err) reject(err); else resolve(script); }
|
最後如果上面範例載入成功,應就可以在 console.log 裡面看到以下的內容
如果,把上面的網址改成
htxxxwefetps://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js
這時候看 console.log 裡面的結果如下
Change promise-base into a helper
那如果,在專案中有多處都會需要有這種將原本 callback-base 的function 轉成 promise-base 的功能的化,那我們應該要把這種轉化的功能寫成一個公用的 helper。
首先先來看看, callback-base 的範例
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const getSumAsync = (num1, num2, callback) => { if(!num1 || !num2) return callback(new Error('Missing arguments'), null) ; return callback(null, num1+ num2); }
getSumAsync(1, 2, (err, result) => { if(err) { alert(err); } else { console.log(result); // 3 } })
|
Wrap into a promise
把上面的 callback-base function 改成用 Promise 的 resolve 和 reject 來回傳錯誤或執行成功的結果
1 2 3 4 5 6 7 8 9 10
| const getSumAsync = (num1, num2) => { return new Promise((resolve, reject) => { if(!num1 || !num2) reject(new Error(`Missing arguments`)); resolve(num1 + num2); }) }
getSumAsync(1, null) .then(res => console.log(res)) .catch(alert)
|
Promisify
接下來,會跟著另一篇文章拆解原本這一篇教學裡面寫的 promisify function 的流程,一步一步操作。
NodeJS’s promisify
先來看看 Node.js 的 util.promisify()
的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const util = require('util');
const getSumAsync = (num1, num2, callback) => { if(!num1 || !num2) return callback(new Error('Missing arguments'), null) ; return callback(null, num1+ num2); }
const getSumPromise = util.promisify(getSumAsync); // setp1 getSumPromise(1, 1) // setp2 .then(result => { console.log(result); }) .catch(err => { // error hadnling })
|
自訂義 promisify function
接下來,我們就來藉由解析 NodeJS 的 promisify 的方法,進而來撰寫自己的 promisify function
看看 step 1.
NodeJs 的 promisify function 會先丟進 function,由此先來定義一個可以丟入 function 的參數的 function
1 2 3
| const myPromisify = fn => {};
const getSumPromise = myPromisify(getSumAsync);
|
看看 step 2.
getSumPromise(1, 1) 是一個呼叫 function 的用法,如此一來,我們知道自訂義的 myPromisify 要回傳一個 function。
1 2 3 4 5
| const myPromisify = fn => { return (...args) {
} };
|
這邊的細節為在回傳的 function 的參數中,我們使用了展開餘數的方式做撰寫,因為,我們並不知道當外部在使用的時候會傳入多少的參數進來。
那 getSumPromise(1, 1) 後面是直接接了 chaining .then
和 .catch
,由此可知我們剛剛在 myPromisify 裡面定義的那個回傳的 function ,它自身會回傳一個 Promise 物件,如此一來,才有辦法做 Promise chaining 的操作。
故再加上回傳 Promise 物件的內容,會變得像下面這樣
1 2 3 4 5 6 7
| const myPromise = (fn) => { return (...args) => { return new Promise(resolv, reject) { // executor } } }
|
接下來,要如何得知是什麼時機點要呼叫回傳 Promise 的 resolve 和 reject 的?
這個時機點,就需要靠傳入被 promisify 的 function 的 callback function 來決定囉,寫法如下
1 2 3 4 5 6 7 8 9 10
| const myPromise = (fn) => { return (...args) => { return new Promise((resolve, reject) => { function callbackFnc(err, result) { if(err) reject(err); // 有 err 傳入 callbackFnc 時,呼叫 reject else resolve(result); // 有 result 傳入 callbackFnc 時,呼叫 resolve } }) } }
|
但是,傳入 myPromise 的 function,本身並不知道 callbackFnc 的存在,所以,我們會透過將 callbackFnc 一併塞入 args 的做法,來讓傳入 myPromise 的 function,不僅獲得傳入參數的內容,同時,也可以使用 callbackFnc。
寫法如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const myPromise = (fn) => { return (...args) => { return new Promise(resolve, reject) { function callbackFnc(err, script) { if(err) reject(err); else resolve(result); }
args.push(callbackFnc); fn.call(this, ...args); } } }
const getSumAync = (num1, num2, callback) => { if(!num1 || !num2) { return callback(new Error('Missing arguments'), null); } return callback(null, num1 + num2); }
const getSumAsync = myPromise(getSumAync); getSumAsync(1, 2) .then(res => console.log(res)) .catch(alert)
|
最後,我們加入了 [fn.call](http://fn.call)(this, ...args)
它的功用,
不僅是透過 fn.call 來執行傳入 myPromise 的 function 之外,
我們在 call
第一個傳入參數 this
是 window 物件,
第二個參數我們利用展開餘數,將傳入 args 展開,如此一來,它就會按照順序的為 getSumAync = (num1, num2, callback)
的三個參數依序賦值。
以上的步驟,就是撰寫我們自己的 promisify function 的流程。
若傳入的 callback function 會傳入多組結果的話怎麼辦?
也就是說 callbackFnc(err, result) 不再只有一個 result 參數,它會傳入多個像這樣 callbackFnc(err, result1, result2 …)
那我們就要做以下的改寫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| const myPromise = (fn) => { return (...args) => { return new Promise((resolve, reject) => { function callbackFnc(err, ...results) { if(err) reject(err); else resolve(results.length === 1 ? results[0]:results); } args.push(callbackFnc); fn.call(this, ...args); }) } }
const getSumAync = (num1, num2, callback) => { if(!num1 || !num2) { return callback(new Error('Missing arguments'), null); } const sum = num1 + num2; const message = `Sum is ${sum}` return callback(null, sum, message); }
const getSumAsync = myPromise(getSumAync); getSumAsync(1, 2) .then(res => console.log(res)) // [3, "Sum is 3"] .catch(alert)
|
以上就是拆解 promisify function 的經過囉~
Reference
- https://javascript.info/promisify
- 詳細拆解 promisify function 製作步驟的文章