用Promise講一個(gè)悲傷的故事給你聽(tīng)
那天我正在學(xué)習(xí) Promise,突然家里打電話過(guò)來(lái)說(shuō),家里蓋房子要錢(qián)。我工作這么多年了,從事著別人眼中高薪工作,于是滿口答應(yīng)下來(lái)。但是由于我并沒(méi)有錢(qián),于是我跟家里說(shuō),等過(guò)幾天我再打錢(qián)過(guò)去。我也好乘著這幾天想想辦法。
首先我找到我的同學(xué)李雷,他現(xiàn)在一個(gè)部門(mén)經(jīng)理了,我想應(yīng)該他應(yīng)該有錢(qián)。我跟他說(shuō)明了借錢(qián)的意向,李雷二話不說(shuō)就答應(yīng)借我300,不過(guò)同時(shí)表示要回家跟老婆商量商量,我說(shuō)好。此時(shí)我想起來(lái)答應(yīng)或者說(shuō)承諾的英文單詞就是 Promise 。承諾的結(jié)果是錢(qián),錢(qián)是數(shù)值(number 類型)。于是我想把我要借錢(qián)的這一行為寫(xiě)成一個(gè) TypeScript 函數(shù)如下:
- // 向李雷借錢(qián),李雷丟給我一個(gè)承諾
- function borrowMoneyFromLiLei(): Promise<number> {
- return new Promise<number>(function(fulfill, reject) {
- // 李雷跟老婆商量中
- });
- }
此時(shí),我在想李雷老婆會(huì)答應(yīng)給我借300塊嗎?我不確定,就像薛定諤的貓。借還是不借,這是一個(gè)問(wèn)題。然后我發(fā)現(xiàn)這也可以寫(xiě)成一個(gè)函數(shù)。借或者不借用布爾值來(lái)表示 (boolean 類型)。函數(shù)如下:
- // 李雷的老婆是否會(huì)答應(yīng)給我借錢(qián)?
- function willLiLeiWifeLendMeMoeny(): Promise<boolean> {
- return new Promise<boolean>(function(lend, reject) {
- // 借還是不借
- });
- }
如果李雷借我錢(qián)了,我就轉(zhuǎn)錢(qián)給家里,沒(méi)有,我應(yīng)該要再去找別人借了。可以用下面的函數(shù)描述我此時(shí)的處境。
- function transferMoneyToHome(money: number) {
- // 給家里轉(zhuǎn)錢(qián)
- }
- function mySituation(){
- borrowMoneyFromLiLei()
- .then((money:number) => {
- // 如果李雷借我錢(qián)了,我就轉(zhuǎn)錢(qián)給家里.
- transferMoneyToHome(money)
- }).catch((reason) => {
- // 李雷老婆拒絕借錢(qián)給我。 那我應(yīng)該考慮向其他人借了。
- borrowMoneyFromOthers()
- })
- }
找其他人借,我能想到就(張三,李四,五五)這三個(gè)人了,其他的朋友很少聯(lián)系,突然說(shuō)借錢(qián)也不好。于是我嘗試向他們借錢(qián)。用代碼表示是這樣子的:
- function borrowMoneyFromOthers() {
- // 我先試著向張三借
- tryBorrowMoneyFromZhangshan()
- .then(money => {
- transferMoneyToHome(money);
- })
- .catch(reason => {
- // 如果張三不借,并丟給我一個(gè)理由
- // 試著向李四借
- tryBorrowMoneyFromLisi()
- .then(money => {
- transferMoneyToHome(money);
- })
- .catch(reason2 => {
- // 如果 李四也不肯錯(cuò)
- // 再試試向王五借
- tryBorrowMoneyFromWangwu()
- .then(money => {
- transferMoneyToHome(money);
- })
- .catch(reason => {
- // 沒(méi)有人肯借
- throw new Error("我該怎么辦呢?");
- });
- });
- });
- }
由于借著錢(qián)之后都是向家里轉(zhuǎn)錢(qián),所以上面的代碼應(yīng)該簡(jiǎn)化一下。簡(jiǎn)化后如下:
- function borrowMoneyFromOthers() {
- // 我先試著向張三借
- tryBorrowMoneyFromZhangshan()
- .then(transferMoneyToHome)
- .catch(reason => {
- // 如果張三不借,并丟給我一個(gè)理由
- // 試著向李四借
- tryBorrowMoneyFromLisi()
- .then(transferMoneyToHome)
- .catch(reason2 => {
- // 如果 李四也不肯錯(cuò)
- // 再試試向王五借
- tryBorrowMoneyFromWangwu()
- .then(transferMoneyToHome)
- .catch(reason => {
- // 沒(méi)有人肯借
- throw new Error("我該怎么辦呢?");
- });
- });
- });
- }
在上面的思路中,我是一個(gè)一個(gè)找他們借錢(qián)的,一個(gè)借不著再找另一個(gè)。我為什么不同時(shí)找他們借呢?誰(shuí)借我了,我就轉(zhuǎn)錢(qián)給家里。此時(shí)我想起了剛學(xué)的 Promise.race 方法,也許這個(gè)方法可以幫助我表達(dá)我的這一決策需求.
- function borrowMoneyFromOthers() {
- // 同時(shí)向張三,李四,王五借錢(qián),只要有人借我錢(qián)了,我就轉(zhuǎn)錢(qián)給家里。
- Promise.race([
- tryBorrowMoneyFromZhangshan(),
- tryBorrowMoneyFromLisi(),
- tryBorrowMoneyFromWangwu()
- ])
- .then(transferMoneyToHome)
- .catch(reasons => {
- console.warn("沒(méi)一個(gè)人愿意給我借錢(qián),他們理由是:", reasons);
- });
- }
我用timeout 模擬一下他們給我答復(fù)的,代碼如下:
- // 嘗試找張三借
- function tryBorrowMoneyFromZhangshan(): Promise<number> {
- return new Promise(function(fulfill, reject) {
- setTimeout(() => {
- fulfill(300);
- }, 100);
- });
- }
- // 嘗試找李四借
- function tryBorrowMoneyFromLisi(): Promise<number> {
- return new Promise(function(fulfill, reject) {
- setTimeout(() => {
- reject("對(duì)不起我也沒(méi)錢(qián)");
- }, 50);
- });
- }
- // 嘗試找王五借
- function tryBorrowMoneyFromWangwu(): Promise<number> {
- return new Promise(function(fulfill, reject) {
- setTimeout(() => {
- fulfill(300);
- }, 500);
- });
- }
結(jié)果運(yùn)行之后,控制臺(tái)輸出的是:
沒(méi)一個(gè)人愿意給我借錢(qián),他們理由是: 對(duì)不起我也沒(méi)錢(qián)
看來(lái) Promise.race 適用用來(lái)模擬搶答,而不是選擇最優(yōu)解。 比如多人搶答一個(gè)問(wèn)題,第一個(gè)搶答之后不論他回答的是否是正確,這個(gè)題都過(guò)了。
不過(guò)沒(méi)關(guān)系。也許我可以自己寫(xiě)一個(gè)來(lái)叫做 promiseOne 的函數(shù)來(lái)實(shí)現(xiàn)這個(gè)功能。代碼如下:
- /**
- * 當(dāng)其中一個(gè) Promise 兌現(xiàn)時(shí),返回的 Promise 即被兌現(xiàn)
- * @param promises Promise<T> 的數(shù)組
- */
- function promiseOne<T>(promises: Promise<T>[]): Promise<T> {
- const promiseCount = promises.length;
- return new Promise<T>(function(resolve, reject) {
- const reasons: any[] = [];
- let rejectedCount = 0;
- promises.forEach((promise, index) => {
- promise.then(resolve).catch(reason => {
- reasons[index] = reason;
- rejectedCount++;
- if (rejectedCount === promiseCount) {
- reject(reasons);
- }
- });
- });
- });
- }
正當(dāng)我寫(xiě)完了上面的代碼,他們?nèi)齻€(gè)給我回話了,說(shuō)是現(xiàn)在手上也沒(méi)有那么多錢(qián),但是可以給我借100. 于是我現(xiàn)在需要處理這樣的事情,就是當(dāng)他們?nèi)齻€(gè)人把錢(qián)都轉(zhuǎn)給我之后我再轉(zhuǎn)給家里。 當(dāng)他們?nèi)齻€(gè)都兌換借我100塊錢(qián)的承諾時(shí),可以用 Promise.all 來(lái)表示,代碼如下:
- function borrowMoneyFromOthers() {
- // 同時(shí)向張三,李四,王五借錢(qián), 借到之后,我就轉(zhuǎn)錢(qián)給家里。
- Promise.all([
- tryBorrowMoneyFromZhangshan(),
- tryBorrowMoneyFromLisi(),
- tryBorrowMoneyFromWangwu()
- ])
- .then(moneyArray => {
- console.info("借到錢(qián)啦:", moneyArray);
- const totalMoney = moneyArray.reduce((acc, cur) => acc + cur);
- transferMoneyToHome(totalMoney);
- })
- .catch(reasons => {
- console.warn("有人不愿意給我借錢(qián),理由是:", reasons);
- });
- }
現(xiàn)在有三個(gè)人愿意給我借錢(qián)了,嗯,也就是說(shuō)我借到了 300 塊。然而這錢(qián)用來(lái)建房還是杯水車薪。所以我還得想辦法。我想我要不要試試用這300塊來(lái)買(mǎi)一下彩票。如果中了,說(shuō)不定這事就成了。
- function buyLottery(bet: number): Promise<number> {
- return new Promise(function(fulfill, resolve) {
- // 投注
- // 等待開(kāi)獎(jiǎng)
- setTimeout(() => {
- resolve("很遺憾你沒(méi)有買(mǎi)中");
- }, 100);
- });
- }
- function borrowMoneyFromOthers() {
- // 同時(shí)向張三,李四,王五借錢(qián),
- Promise.all([
- tryBorrowMoneyFromZhangshan(),
- tryBorrowMoneyFromLisi(),
- tryBorrowMoneyFromWangwu()
- ])
- .then(moneyArray => {
- console.info("借到錢(qián)啦:", moneyArray);
- const totalMoney = moneyArray.reduce((acc, cur) => acc + cur);
- // 購(gòu)買(mǎi)彩票
- buyLottery(totalMoney)
- .then(transferMoneyToHome)
- .catch(reason => {
- console.log("沒(méi)中,", reason);
- });
- })
- .catch(reasons => {
- console.warn("有人不愿意給我借錢(qián),理由是:", reasons);
- });
- }
我知道很大概率我是買(mǎi)不中的,最近世界杯開(kāi)賽了,我幻想著壓注世界杯,而且世界杯場(chǎng)次多,一天好幾場(chǎng),一場(chǎng)買(mǎi)中的盈利還可以投入到下一場(chǎng)。我把我的幻想寫(xiě)成代碼,大概就是下面這樣。
- function betWorldCup() {
- // 初始資金 300 塊
- Promise.resolve(300)
- .then(moeny => {
- // 投西班牙
- return new Promise<number>(function(fulfil, reject) {
- setTimeout(() => {
- // 假假設(shè) 賠率 1.2
- fulfil(moeny * 1.2);
- }, 100);
- });
- })
- .then(ret => {
- // 投英格蘭
- return ret * 1.2;
- })
- .then(ret => {
- // 投巴西
- return new Promise<number>(function(fulfil, reject) {
- setTimeout(() => {
- fulfil(ret * 1.2);
- }, 92);
- });
- })
- .then(ret => {
- console.log("現(xiàn)在收益加本金共有: ", ret);
- });
- }
我想,如果第一場(chǎng)投失敗了,應(yīng)該再給自己一次機(jī)會(huì)。于是將代碼修改如下:
- function betWorldCup() {
- // 初始資金 300 塊
- Promise.resolve(300)
- .then(moeny => {
- // 投西班牙
- return new Promise<number>(function(fulfil, reject) {
- setTimeout(() => {
- // 假假設(shè) 賠率 1.2
- // fulfil(moeny * 1.2);
- reject("莊家跑跑路了");
- }, 100);
- });
- })
- .then(
- ret => {
- // 投英格蘭
- return ret * 1.2;
- },
- reason => {
- console.info("第一次投注失敗,再給一次機(jī)會(huì)好不好?, 失敗原因: ", reason);
- // 再投 300
- return 300;
- }
- )
- .then(ret => {
- // 投巴西
- return new Promise<number>(function(fulfil, reject) {
- setTimeout(() => {
- fulfil(ret * 1.2);
- }, 92);
- });
- })
- .then(ret => {
- console.log("現(xiàn)在收益加本金共有: ", ret);
- throw new Error("不要再買(mǎi)了");
- })
- .then(ret => {
- console.info("準(zhǔn)備再買(mǎi)嗎?");
- })
- .catch(reason => {
- console.log("出錯(cuò)了:", reason);
- });
- }
此時(shí)如下運(yùn)行上面的函數(shù)會(huì)得到如下輸出:
- 第一次投注失敗,再給一次機(jī)會(huì)好不好?, 失敗原因: 莊家跑跑路了
- 現(xiàn)在收益加本金共有: 360
- 出錯(cuò)了:
- Error: 不要再買(mǎi)了
然而,幻想結(jié)束之后,我依然得苦苦思考怎么樣籌錢(qián)。