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