您好,登錄后才能下訂單哦!
我們經(jīng)常會遇到這種情況:比如通過用戶名查找并返回該用戶信息和他的關(guān)注者。通常有兩種方法:
定義一個外部變量:
var usergetUserByName('nswbmw') .then((_user) => { user = _user return getFollowersByUserId(user._id) }) .then((followers) => { return { user, followers } })
使用閉包:
getUserByName('nswbmw') .then((user) => { return getFollowersByUserId(user._id).then((followers) => { return { user, followers } }) })
兩種實現(xiàn)都可以,但都不太美觀。于是我之前產(chǎn)生了一個想法:同一層的 then 的參數(shù)是之前所有 then 結(jié)果的逆序。體現(xiàn)在代碼上就是:
Promise.resolve() .then(function () { return getUserByName('nswbmw') }) .then(function (user) { return getFollowersByUserId(user._id) }) .then((followers, user) => { return { user, followers } })
第 3 個 then 的參數(shù)是前兩個 then 結(jié)果的逆序,即 followers 和 user。更復(fù)雜比如嵌套 promise 的我就不列了,這種實現(xiàn)的要點在于:如何區(qū)分 then 的層級。從 appoint 的實現(xiàn)我們知道,每個 then 返回一個新的 promise,這導致了無法知道當前 then 來自之前嵌套多深的 promise。所以這個想法無法實現(xiàn)。
后來,我又想出了一種比上面更好的一種解決方法,即命名 Promise:當前 then 的第一個參數(shù)仍然是上個 promise 的返回值(即兼容 Promise/A+ 規(guī)范),后面的參數(shù)使用依賴注入。體現(xiàn)在代碼上就是:
Promise.resolve() .then(function user() { return getUserByName('nswbmw') }) .then(function followers(_, user) { return getFollowersByUserId(user._id) }) .then((_, user, followers) => { return { user, followers } })
上面通過給 then 的回調(diào)函數(shù)命名(如:user),該回調(diào)函數(shù)的返回值掛載到 promise 內(nèi)部變量上(如:values: { user: 'xxx'} ),并把父 promise 的 values 往子 promise 傳遞。then 的第二個之后的參數(shù)通過依賴注入實現(xiàn)注入,這就是命名 Promise 實現(xiàn)的基本思路。我們可以給 Promise 構(gòu)造函數(shù)的參數(shù)、then 回調(diào)函數(shù)和 catch 回調(diào)函數(shù)命名。
于是,我在 appoint 包基礎(chǔ)上修改并發(fā)布了 named-appoint 包。
named-appoint 原理:給 promise 添加了 name 和 values 屬性,name 是該 promise 的標識(取 Promise 構(gòu)造函數(shù)的參數(shù)、then 回調(diào)函數(shù)或 catch 回調(diào)函數(shù)的名字),values 是個對象存儲了所有祖先 promise 的 name 和 value。當父 promise 狀態(tài)改變時,設(shè)置父 promise 的 value 和 values( this.values[this.name] = value),然后將 values 拷貝到子 promise 的 values,依次往下傳遞。再看個例子:
const assert = require('assert')const Promise = require('named-appoint')new Promise(function username(resolve, reject) { setTimeout(() => { resolve('nswbmw') })}).then(function user(_, username) { assert(_ === 'nswbmw') assert(username === 'nswbmw') return { name: 'nswbmw', age: '17' }}).then(function followers(_, username, user) { assert.deepEqual(_, { name: 'nswbmw', age: '17' }) assert(username === 'nswbmw') assert.deepEqual(user, { name: 'nswbmw', age: '17' }) return [ { name: 'zhangsan', age: '17' }, { name: 'lisi', age: '18' } ]}).then((_, user, followers, username) => { assert.deepEqual(_, [ { name: 'zhangsan', age: '17' }, { name: 'lisi', age: '18' } ]) assert(username === 'nswbmw') assert.deepEqual(user, { name: 'nswbmw', age: '17' }) assert.deepEqual(followers, [ { name: 'zhangsan', age: '17' }, { name: 'lisi', age: '18' } ])}).catch(console.error)
很明顯,命名 Promise 有個前提條件是:在同一條 promise 鏈上。如下代碼:
const assert = require('assert')const Promise = require('named-appoint')new Promise(function username(resolve, reject) { setTimeout(() => { resolve('nswbmw') })}).then(() => { return Promise.resolve() .then(function user(_, username) { assert(username === undefined) return { name: 'nswbmw', age: '17' } })}).then(function (_, username, user) { assert.deepEqual(_, { name: 'nswbmw', age: '17' }) assert(username === 'nswbmw') assert(user === undefined)}).catch(console.error)
最后一個 then 打印 undefined,因為內(nèi)部產(chǎn)生了一條新的 promise 鏈分支。
結(jié)合 co 使用
與 co 結(jié)合使用是沒有什么變化的,如:
const Promise = require('named-appoint')const co = require('co')const promise = Promise.resolve() .then(function user() { return 'nswbmw' }) .then(function followers() { return [{ name: 'zhangsan' }, { name: 'lisi' }] }) .then((_, user, followers) => { return { user, followers } })co(function *() { console.log(yield promise) /* { user: 'nswbmw', followers: [ { name: 'zhangsan' }, { name: 'lisi' } ] } */}).catch(console.error)
順便擅自制定了一個 Promise/A++ 規(guī)范。
我們繼續(xù)腦洞一下。Swift 中錯誤處理是這樣的:
do { try getFollowers("nswbmw") } catch AccountError.No_User { print("No user") } catch AccountError.No_followers { print("No followers") } catch { print("Other error") }
可以設(shè)定 catch 只捕獲特定異常的錯誤,如果之前的 catch 沒有捕獲錯誤,那么錯誤將會被最后那個 catch 捕獲。通過命名 catch 回調(diào)函數(shù) JavaScript 也可以實現(xiàn)類似的功能,我在 appoint 的基礎(chǔ)上修改并發(fā)布了 condition-appoint 包??磦€例子:
var Promise = require('condition-appoint')Promise.reject(new TypeError('type error')) .catch(function SyntaxError(e) { console.error('SyntaxError: ', e) }) .catch(function TypeError(e) { console.error('TypeError: ', e) }) .catch(function (e) { console.error('default: ', e) })
將會被第二個 catch 捕獲,即打?。?/p>
TypeError: [TypeError: type error]
修改一下:
var Promise = require('condition-appoint')Promise.reject(new TypeError('type error')) .catch(function SyntaxError(e) { console.error('SyntaxError: ', e) }) .catch(function ReferenceError(e) { console.error('ReferenceError: ', e) }) .catch(function (e) { console.error('default: ', e) })
將會被第三個 catch 捕獲,即打?。?/p>
default: [TypeError: type error]
因為沒有對應(yīng)的錯誤 catch 函數(shù),所以最終被一個匿名的 catch 捕獲。再修改一下:
var Promise = require('condition-appoint')Promise.reject(new TypeError('type error')) .catch(function SyntaxError(e) { console.error('SyntaxError: ', e) }) .catch(function (e) { console.error('default: ', e) }) .catch(function TypeError(e) { console.error('TypeError: ', e) })
將會被第二個 catch 捕獲,即打?。?/p>
default: [TypeError: type error]
因為提前被匿名的 catch 方法捕獲。
condition-appoint 實現(xiàn)原理很簡單,就在 appoint 的 then 里加了 3 行代碼:
Promise.prototype.then = function (onFulfilled, onRejected) { ... if (isFunction(onRejected) && this.state === REJECTED) { if (onRejected.name && ((this.value && this.value.name) !== onRejected.name)) { return this; } } ...};
判斷傳入的回調(diào)函數(shù)名和錯誤名是否相等,不是匿名函數(shù)且不相等則通過 return this 跳過這個 catch 語句,即實現(xiàn)值穿透。
當然,condition-appoint 對自定義錯誤也有效,只要自定義錯誤設(shè)置了 name 屬性。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。