用 Promise 描述一個悲傷的故事

banxi發表於2018-06-22

那天我正在學習 Promise,突然家裡打電話過來說,家裡蓋房子要錢。我工作這麼多年了,從事著別人眼中高薪工作,於是滿口答應下來。但是由於我並沒有錢,於是我跟家裡說,等過幾天我再打錢過去。我也好乘著這幾天想想辦法。

首先我找到我的同學李雷,他現在一個部門經理了,我想應該他應該有錢。我跟他說明了借錢的意向,李雷二話不說就答應借我300,不過同時表示要回家跟老婆商量商量,我說好。此時我想起來答應或者說承諾的英文單詞就是 Promise。承諾的結果是錢,錢是數值(number 型別)。於是我想把我要借錢的這一行為寫成一個TypeScript 函式如下:

// 向李雷借錢,李雷丟給我一個承諾
function borrowMoneyFromLiLei(): Promise<number> {
  return new Promise<number>(function(fulfill, reject) {
     // 李雷跟老婆商量中
  });
}

複製程式碼

此時,我在想李雷老婆會答應給我借300塊嗎?我不確定,就像薛定諤的貓。借還是不借,這是一個問題。然後我發現這也可以寫成一個函式。借或者不借用布林值來表示 (boolean 型別)。函式如下:

// 李雷的老婆是否會答應給我借錢?
function willLiLeiWifeLendMeMoeny(): Promise<boolean> {
  return new Promise<boolean>(function(lend, reject) {
    // 借還是不借
  });
}
複製程式碼

如果李雷借我錢了,我就轉錢給家裡,沒有,我應該要再去找別人借了。可以用下面的函式描述我此時的處境。

function transferMoneyToHome(money: number) {
    // 給家裡轉錢
}
function mySituation(){
    borrowMoneyFromLiLei()
    .then((money:number) => {
        // 如果李雷借我錢了,我就轉錢給家裡.
        transferMoneyToHome(money)
    }).catch((reason) => {
        // 李雷老婆拒絕借錢給我。 那我應該考慮向其他人借了。
        borrowMoneyFromOthers()
    })
}

複製程式碼

找其他人借,我能想到就(張三,李四,五五)這三個人了,其他的朋友很少聯絡,突然說借錢也不好。於是我嘗試向他們借錢。用程式碼表示是這樣子的:

function borrowMoneyFromOthers() {
  // 我先試著向張三借
  tryBorrowMoneyFromZhangshan()
    .then(money => {
      transferMoneyToHome(money);
    })
    .catch(reason => {
      // 如果張三不借,並丟給我一個理由
      // 試著向李四借
      tryBorrowMoneyFromLisi()
        .then(money => {
          transferMoneyToHome(money);
        })
        .catch(reason2 => {
          // 如果 李四也不肯錯
          // 再試試向王五借
          tryBorrowMoneyFromWangwu()
            .then(money => {
              transferMoneyToHome(money);
            })
            .catch(reason => {
              // 沒有人肯借
              throw new Error("我該怎麼辦呢?");
            });
        });
    });
}

複製程式碼

由於藉著錢之後都是向家裡轉錢,所以上面的程式碼應該簡化一下。簡化後如下:

function borrowMoneyFromOthers() {
  // 我先試著向張三借
  tryBorrowMoneyFromZhangshan()
    .then(transferMoneyToHome)
    .catch(reason => {
      // 如果張三不借,並丟給我一個理由
      // 試著向李四借
      tryBorrowMoneyFromLisi()
        .then(transferMoneyToHome)
        .catch(reason2 => {
          // 如果 李四也不肯錯
          // 再試試向王五借
          tryBorrowMoneyFromWangwu()
            .then(transferMoneyToHome)
            .catch(reason => {
              // 沒有人肯借
              throw new Error("我該怎麼辦呢?");
            });
        });
    });
}
複製程式碼

在上面的思路中,我是一個一個找他們借錢的,一個借不著再找另一個。我為什麼不同時找他們借呢?誰借我了,我就轉錢給家裡。此時我想起了剛學的Promise.race 方法,也許這個方法可以幫助我表達我的這一決策需求.

function borrowMoneyFromOthers() {
  // 同時向張三,李四,王五借錢,只要有人借我錢了,我就轉錢給家裡。
  Promise.race([
    tryBorrowMoneyFromZhangshan(),
    tryBorrowMoneyFromLisi(),
    tryBorrowMoneyFromWangwu()
  ])
    .then(transferMoneyToHome)
    .catch(reasons => {
      console.warn("沒一個人願意給我借錢,他們理由是:", reasons);
    });
}

複製程式碼

我用timeout 模擬一下他們給我答覆的,程式碼如下:

// 嘗試找張三借
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("對不起我也沒錢");
    }, 50);
  });
}
// 嘗試找王五借
function tryBorrowMoneyFromWangwu(): Promise<number> {
  return new Promise(function(fulfill, reject) {
    setTimeout(() => {
      fulfill(300);
    }, 500);
  });
}
複製程式碼

結果執行之後,控制檯輸出的是:

沒一個人願意給我借錢,他們理由是: 對不起我也沒錢

看來 Promise.race 適用用來模擬搶答,而不是選擇最優解。 比如多人搶答一個問題,第一個搶答之後不論他回答的是否是正確,這個題都過了。

不過沒關係。也許我可以自己寫一個來叫做 promiseOne 的函式來實現這個功能。程式碼如下:


/**
 * 當其中一個 Promise 兌現時,返回的 Promise 即被兌現
 * @param promises Promise<T> 的陣列
 */
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);
        }
      });
    });
  });
}
複製程式碼

正當我寫完了上面的程式碼,他們三個給我回話了,說是現在手上也沒有那麼多錢,但是可以給我借100. 於是我現在需要處理這樣的事情,就是當他們三個人把錢都轉給我之後我再轉給家裡。 當他們三個都兌換借我100塊錢的承諾時,可以用 Promise.all 來表示,程式碼如下:

function borrowMoneyFromOthers() {
  // 同時向張三,李四,王五借錢, 借到之後,我就轉錢給家裡。
  Promise.all([
    tryBorrowMoneyFromZhangshan(),
    tryBorrowMoneyFromLisi(),
    tryBorrowMoneyFromWangwu()
  ])
    .then(moneyArray => {
      console.info("借到錢啦:", moneyArray);
      const totalMoney = moneyArray.reduce((acc, cur) => acc + cur);
      transferMoneyToHome(totalMoney);
    })
    .catch(reasons => {
      console.warn("有人不願意給我借錢,理由是:", reasons);
    });
}
複製程式碼

現在有三個人願意給我借錢了,嗯,也就是說我借到了 300 塊。然而這錢用來建房還是杯水車薪。所以我還得想辦法。我想我要不要試試用這300塊來買一下彩票。如果中了,說不定這事就成了。

function buyLottery(bet: number): Promise<number> {
  return new Promise(function(fulfill, resolve) {
    // 投注
    // 等待開獎
    setTimeout(() => {
      resolve("很遺憾你沒有買中");
    }, 100);
  });
}

function borrowMoneyFromOthers() {
  // 同時向張三,李四,王五借錢, 
  Promise.all([
    tryBorrowMoneyFromZhangshan(),
    tryBorrowMoneyFromLisi(),
    tryBorrowMoneyFromWangwu()
  ])
    .then(moneyArray => {
      console.info("借到錢啦:", moneyArray);
      const totalMoney = moneyArray.reduce((acc, cur) => acc + cur);
      // 購買彩票
      buyLottery(totalMoney)
        .then(transferMoneyToHome)
        .catch(reason => {
          console.log("沒中,", reason);
        });
    })
    .catch(reasons => {
      console.warn("有人不願意給我借錢,理由是:", reasons);
    });
}
複製程式碼

我知道很大概率我是買不中的,最近世界盃開賽了,我幻想著壓注世界盃,而且世界盃場次多,一天好幾場,一場買中的盈利還可以投入到下一場。我把我的幻想寫成程式碼,大概就是下面這樣。

function betWorldCup() {
  // 初始資金 300 塊
  Promise.resolve(300)
    .then(moeny => {
      // 投西班牙
      return new Promise<number>(function(fulfil, reject) {
        setTimeout(() => {
          // 假假設 賠率 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("現在收益加本金共有: ", ret);
    });
}
複製程式碼

我想,如果第一場投失敗了,應該再給自己一次機會。於是將程式碼修改如下:

function betWorldCup() {
  // 初始資金 300 塊
  Promise.resolve(300)
    .then(moeny => {
      // 投西班牙
      return new Promise<number>(function(fulfil, reject) {
        setTimeout(() => {
          // 假假設 賠率 1.2
          // fulfil(moeny * 1.2);
          reject("莊家跑跑路了");
        }, 100);
      });
    })
    .then(
      ret => {
        // 投英格蘭
        return ret * 1.2;
      },
      reason => {
        console.info("第一次投注失敗,再給一次機會好不好?, 失敗原因: ", reason);
        // 再投 300
        return 300;
      }
    )
    .then(ret => {
      // 投巴西
      return new Promise<number>(function(fulfil, reject) {
        setTimeout(() => {
          fulfil(ret * 1.2);
        }, 92);
      });
    })
    .then(ret => {
      console.log("現在收益加本金共有: ", ret);
      throw new Error("不要再買了");
    })
    .then(ret => {
      console.info("準備再買嗎?");
    })
    .catch(reason => {
      console.log("出錯了:", reason);
    });
}
複製程式碼

此時如下執行上面的函式會得到如下輸出:

第一次投注失敗,再給一次機會好不好?, 失敗原因:  莊家跑跑路了
現在收益加本金共有:  360
出錯了:
Error: 不要再買了
複製程式碼

然而,幻想結束之後,我依然得苦苦思考怎麼樣籌錢。

相關文章