2016-07-28 50 views
0

我正在嘗試使用Mongoose內置的promise支持來爲發送好友請求的用戶編寫一些乾淨的Javascript代碼。但是,當我嘗試確保正確的錯誤處理和順序性時,我仍然會得到一個(比正常情況稍小的)厄運金字塔。如何promisify這個Mongoose代碼?

在這裏,我首先確保好友請求有效,然後將目標ID保存到請求者發送的請求,然後,如果保存成功,請將請求者的ID保存到目標的好友請求中。

是否需要使用像q這樣的第三方庫才能儘可能乾淨地完成此操作?我該如何構造這樣的結構,才能在最後使用傳統的單錯誤處理程序?

function _addFriend (requesterId, targetId) { 
// (integer, integer) 
User.findById(requesterId) 
.exec((requester) => { 
    if (!(targetId in requester.friends 
    || targetId in requester.sentfriendRequests 
    || targetId in requester.friendRequests)) { 
     requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId]) 
     requester.save() 
     .then((err) => { 
     if (err) throw err; 
     User.findById(targetId) 
     .exec((err, target) => { 
      if (err) throw err; 
      target.friendRequests = target.friendRequests.concat([requesterId]) 
      target.save().then(err => {if (err) throw err}) 
      }) 
     }) 
    } 
}) 
} 
+0

你確定第一個'exec'回調函數不需要'err'參數嗎? – Bergi

+0

'.then(err => {if(err)throw err})'看起來不像應該需要承諾 – Bergi

回答

1

在英語中,要做到這一點的方法是使用由exec()返回的承諾有then塊返回的承諾,取消縮進,再加入then那些。更容易在代碼中說...

編輯再次感謝@Bergi使我閱讀和理解應用邏輯。 @Bergi是正確的,必須有一點嵌套才能完成工作,但真正的意義不在於減少嵌套,而在於提高清晰度。

更好的清晰度可以來自因子分解成邏輯部分,包括一些返回承諾。

這些函數隱藏了邏輯所需的承諾嵌套。這並不說明(因爲OP不表示該應用程序應該如何處理),當它拒絕這樣做,由於現有請求應該返回什麼addFriend ...

function _addFriend (requesterId, targetId) { 
    // note - pass no params to exec(), use it's returned promise 
    return User.findById(requesterId).exec().then((requester) => { 
     return canAddFriend(requester, targetId) ? addFriend(requester, targetId) : null; 
    }); 
} 

function canAddFriend(requester, targetId) { 
    return requester && targetId && 
     !(targetId in requester.friends 
      || targetId in requester.sentfriendRequests 
      || targetId in requester.friendRequests); 
} 

function addFriend(requester, targetId) { 
    requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId]); 
    return requester.save().then(() => { 
     return User.findById(targetId).exec(); 
    }).then((target) => { 
     target.friendRequests = target.friendRequests.concat([requesterId]); 
     return target.save(); 
    }); 
} 
+0

你*做了* flubb邏輯,所有跟在'doStuff'之後的東西都應該在'if '身體 – Bergi

+0

謝謝@Bergi,我想我只是錯過了大括號。勒米知道這不是你在說什麼。 – danh

+0

不,我不是指大括號,我的意思是以'requester.save()'開頭的整個動作,需要進入if條件 – Bergi

1

需要一些嵌套在承諾代碼中執行條件,但不如使用基於回調的代碼。

你似乎已經搞砸了if (err) throw err;的東西,你應該永遠不需要承諾。只需使用.then(result => {…}),並且不要再將回調傳遞給exec

如果您始終正確地使用異步函數return承諾(包括then用於鏈接的回調函數),您可以在最後添加單個錯誤處理程序。

function _addFriend (requesterId, targetId) { 
// (integer, integer) 
    return User.findById(requesterId).exec().then(requester => { 
     if (targetId in requester.friends 
      || targetId in requester.sentfriendRequests 
      || targetId in requester.friendRequests) { 
      return; 
     } 
     requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId]) 
     return requester.save().then(() => { 
      return User.findById(targetId).exec() 
     }).then(target => { 
      target.friendRequests = target.friendRequests.concat([requesterId]) 
      return target.save() 
     }); 
    }); 
} 

_addFriend(…).catch(err => { 
    … 
}) 
0

一旦你意識到.exec()返回一個承諾,你可以:

  • 達到預期的扁平化,使代碼更易讀。
  • 避免需要處理「成功」代碼中的錯誤。
  • 處理終端中的錯誤.then()或.catch()。

作爲一種獎勵,你也可以(更容易)爲每個x in y條件投擲有意義的錯誤。

直截了當,你可以寫:

function _addFriend(requesterId, targetId) { 
    return User.findById(requesterId).exec().then(requester => { 
     if (targetId in requester.friends) { 
      throw new Error('target is already a friend'); 
     } 
     if (targetId in requester.sentfriendRequests) { 
      throw new Error('friend request already sent to target'); 
     } 
     if (targetId in requester.friendRequests) { 
      throw new Error('target already sent a friend request to requester'); 
     } 
     requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId]); // or just .push()? 
     return requester.save(); 
    }).then(() => { 
     return User.findById(targetId).exec().then(target => { 
      target.friendRequests = target.friendRequests.concat([requesterId]); // or just .push()? 
      return target.save(); 
     }); 
    }); 
} 

注需要返回到控制流量。

但你可以做得更好。正如上面所寫,被請求的東西可以成功,然後目標的東西失敗,導致數據庫差距。所以你真正想要的是一個db事務來保證兩者都發生或者兩者都不發生。無疑,Mongoose提供了交易,但是您可以在客戶端做一些事情,爲您提供部分利益的交易。

function _addFriend(requesterId, targetId) { 
    return Promise.all([User.findById(requesterId).exec(), User.findById(targetId).exec()]).then(([requester, target]) => { // note destructuring 
     if (targetId in requester.friends) { 
      throw new Error('target is already a friend'); 
     } 
     if (targetId in requester.sentfriendRequests) { 
      throw new Error('friend request already sent to target'); 
     } 
     if (targetId in requester.friendRequests) { 
      throw new Error('target already sent a friend request to requester'); 
     } 
     requester.sentfriendRequests = requester.sentfriendRequests.concat([targetId]); 
     target.friendRequests = target.friendRequests.concat([requesterId]); 
     return requester.save().then(() => { 
      return target.save(); 
     }); 
    }); 
} 

在這裏,你還可以得到(不太可能)的情況是,先救成功,第二保存失敗,但至少你有絕對什麼都不會發生,除非兩個請求者和目標存在的保證。

在這兩種情況下,請撥打如下:

_addFriend(requesterId, targetId).then(function() { 
    // do whatever on success 
}, function(error) { 
    // do whatever on error 
}); 

即使你沒有在現場環境中使用的錯誤信息,他們可以測試/調試時是非常有用的。請檢查他們 - 我可能弄錯了他們。