W3Cschool
恭喜您成為首批注冊用戶
獲得88經驗值獎勵
對于一個簡單的轉換來說 “Promisification” 是一個長單詞。它指將一個接受回調的函數轉換為一個返回 promise 的函數。
由于許多函數和庫都是基于回調的,因此,在實際開發(fā)中經常會需要進行這種轉換。因為使用 promise 更加方便,所以將基于回調的函數和庫 promise 化是有意義的。
為了更好地理解,讓我們來看一個例子。
例如,在 簡介:回調 一章中我們有 loadScript(src, callback)
。
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}`));
document.head.append(script);
}
// 用法:
// loadScript('path/script.js', (err, script) => {...})
該函數通過給定的 src
加載腳本,然后在出現錯誤時調用 callback(err)
,或者在加載成功時調用 callback(null, script)
。這是大家對于使用回調函數的共識,我們之前也學習過。
現在,讓我們將其 promise 化吧。
我們將創(chuàng)建一個新的函數 loadScriptPromise(src)
,與上面的函數作用相同(加載腳本),只是我們創(chuàng)建的這個函數會返回一個 promise 而不是使用回調。
換句話說,我們僅向它傳入 src
(沒有 callback
)并通過該函數的 return 獲得一個 promise,當腳本加載成功時,該 promise 將以 script
為結果 resolve,否則將以出現的 error 為結果 reject。
代碼實現如下:
let loadScriptPromise = function(src) {
return new Promise((resolve, reject) => {
loadScript(src, (err, script) => {
if (err) reject(err);
else resolve(script);
});
});
};
// 用法:
// loadScriptPromise('path/script.js').then(...)
正如我們所看到的,新的函數是對原始的 loadScript
函數的包裝。新函數調用它,并提供了自己的回調來將其轉換成 promise resolve/reject
。
現在 loadScriptPromise
非常適用于基于 promise 的代碼了。如果我們相比于回調函數,更喜歡 promise(稍后我們將看到更多喜歡 promise 的原因),那么我們將改用它。
在實際開發(fā)中,我們可能需要 promise 化很多函數,所以使用一個 helper(輔助函數)很有意義。
我們將其稱為 promisify(f)
:它接受一個需要被 promise 化的函數 f
,并返回一個包裝(wrapper)函數。
function promisify(f) {
return function (...args) { // 返回一個包裝函數(wrapper-function) (*)
return new Promise((resolve, reject) => {
function callback(err, result) { // 我們對 f 的自定義的回調 (**)
if (err) {
reject(err);
} else {
resolve(result);
}
}
args.push(callback); // 將我們的自定義的回調附加到 f 參數(arguments)的末尾
f.call(this, ...args); // 調用原始的函數
});
};
}
// 用法:
let loadScriptPromise = promisify(loadScript);
loadScriptPromise(...).then(...);
代碼看起來可能有些復雜,但其本質與我們在上面寫的那個是一樣的,就是將 loadScript
函數 promise 化。
調用 promisify(f)
會返回一個 f
(*)
的包裝器。該包裝器返回一個 promise,并將調用轉發(fā)給原始的 f
,并在我們自定義的回調 (**)
中跟蹤結果。
在這里,promisify
假設原始函數期望一個帶有兩個參數 (err, result)
的回調。這就是我們最常遇到的形式。那么我們自定義的回調的格式是完全正確的,在這種情況下 promisify
也可以完美地運行。
但是如果原始的 f
期望一個帶有更多參數的回調 callback(err, res1, res2, ...)
,該怎么辦呢?
我們可以繼續(xù)改進我們的輔助函數。讓我們寫一個更高階版本的 promisify
。
promisify(f)
? 的形式調用時,它應該以與上面那個版本的實現的工作方式類似。promisify(f, true)
? 的形式調用時,它應該返回以回調函數數組為結果 resolve 的 promise。這就是具有很多個參數的回調的結果。// promisify(f, true) 來獲取結果數組
function promisify(f, manyArgs = false) {
return function (...args) {
return new Promise((resolve, reject) => {
function callback(err, ...results) { // 我們自定義的 f 的回調
if (err) {
reject(err);
} else {
// 如果 manyArgs 被指定,則使用所有回調的結果 resolve
resolve(manyArgs ? results : results[0]);
}
}
args.push(callback);
f.call(this, ...args);
});
};
}
// 用法:
f = promisify(f, true);
f(...).then(arrayOfResults => ..., err => ...);// promisify(f, true) 來獲取結果數組
function promisify(f, manyArgs = false) {
return function (...args) {
return new Promise((resolve, reject) => {
function callback(err, ...results) { // 我們自定義的 f 的回調
if (err) {
reject(err);
} else {
// 如果 manyArgs 被指定,則使用所有回調的結果 resolve
resolve(manyArgs ? results : results[0]);
}
}
args.push(callback);
f.call(this, ...args);
});
};
}
// 用法:
f = promisify(f, true);
f(...).then(arrayOfResults => ..., err => ...);// promisify(f, true) 來獲取結果數組
function promisify(f, manyArgs = false) {
return function (...args) {
return new Promise((resolve, reject) => {
function callback(err, ...results) { // 我們自定義的 f 的回調
if (err) {
reject(err);
} else {
// 如果 manyArgs 被指定,則使用所有回調的結果 resolve
resolve(manyArgs ? results : results[0]);
}
}
args.push(callback);
f.call(this, ...args);
});
};
}
// 用法:
f = promisify(f, true);
f(...).then(arrayOfResults => ..., err => ...);
正如你所看到的,它與上面那個實現基本相同,只是根據 manyArgs
是否為真來決定僅使用一個還是所有參數調用 resolve
。
對于一些更奇特的回調格式,例如根本沒有 err
的格式:callback(result)
,我們可以手動 promise 化這樣的函數,而不使用 helper。
也有一些具有更靈活一點的 promisification 函數的模塊(module),例如 es6-promisify。在 Node.js 中,有一個內建的 promise 化函數 util.promisify
。
請注意:
Promisification 是一種很好的方法,特別是在你使用
async/await
的時候(請看下一章),但不是回調的完全替代。
請記住,一個 promise 可能只有一個結果,但從技術上講,一個回調可能被調用很多次。
因此,promisification 僅適用于調用一次回調的函數。進一步的調用將被忽略。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯系方式:
更多建議: