es6非同步方法在專案中的實踐

teal-front發表於2019-04-12

es6非同步方法在專案中的實踐

author: teal.yao

polling

/// 輪詢

// promise version
function fetchProtocol_promise(token) {
    let counter = 0;
    return new Promise((resolve, reject) => {
        polling();

        function polling() {
            fetch().then(() => {
                if (data.status === 200) {
                    switch (data.result.state) {
                        case 2:
                            resolve(getPreviewURL(data.result.url))
                            break
                        case 1:
                            if (counter++ > 20) {
                                reject("合同建立超時,請重新再試")
                            }
                            // fetch again
                            setTimeout(polling, 300)
                            break
                        case 3:
                            reject("合同建立失敗")
                            break
                    }
                }
            })
        }
    });
}


/// async version
// 對比promise方式,會多了一個變數來管理狀態成功
// 利用了promise裡面的resolve接收的是一個promise,則會等待此promise的完成
async function fetch(token) {
    let counter = 0;
    return await polling();

    async function polling() {
        if (counter++ > 20) {
            return [false, "合同建立超時,請重新再試"]
        }
        let data = await fetch()
        if (data.status === 200) {
            switch (data.result.state) {
                case 2:
                    return [true, getPreviewURL(data.result.url)]
                case 1:
                    // fetch again
                    return new Promise(resolve =>
                        setTimeout(function () {
                            // waiting for polling state change
                            resolve(polling());
                        }, 300)
                    );
                case 3:
                    return [false, "合同建立失敗"]
            }
        }
    }
}
複製程式碼

給promise設定timeout

/**
 * timeout promise
 * 
 * promise沒有原生的超時的方法,得借用其他方法去處理
 * 
 * 1. promise的兩個最終狀態, fulfilled、 rejected
 * 
 * 2.下面借用Promise.race來處理超時
 * 
 * Promise.race(iterable),當iterable引數裡的任意一個子promise被成功或失敗後,
 * 父promise馬上也會用子promise的成功返回值或失敗詳情作為引數呼叫父promise繫結的相應控制程式碼,
 * 並返回該promise物件。
 */

/// https://italonascimento.github.io/applying-a-timeout-to-your-promises/
const http = require('http')

let get = function (url) {
    return new Promise((resolve, reject) => {
        // 假裝這裡不支援timeout引數
        http.get({
            url,
        }, res => {
            res.on('data', d => {
                resolve(d.toString())
            })
        })
    })
}

/// promise timeout + promise.race
function promiseTimeout(promise, ms) {
    let timeout = new Promise((resolve, reject) => {
        setTimeout(function () {
            reject('timeout')
        }, ms)
    })
    return Promise.race([promise, timeout])
}
promiseTimeout(get('http://github.com'), 5).then(text => {
    console.log({
        text
    })
}).catch(err => {
    console.log({
        err
    })
})
複製程式碼

回撥地獄的解決方案

  /**
   * ======解決回撥地獄的=========
   *  
   * 常用的回撥套回撥,容易出現回撥地獄,造成程式碼的可讀性、維護性差
   * 
   * 現藉助promise來解決
   */


  class Form {
      constructor(num, errMsg) {
          this.num = num
          this.errMsg = errMsg
      }
      validator(callback) {
          callback(this.num < .4)
      }
  }
  let form1 = new Form(.3, 'im .3')
  let form2 = new Form(.7, 'im .7')
  let form3 = new Form(.6, 'im .6')

  // 1. 回撥地獄寫法
  // 回撥之後還是回撥,無窮無盡
  form1.validator(valid => {
      if (valid) {
          form2.validator(valid => {
              if (valid) {
                  form3.validator(valid => {
                      if (valid) {
                          console.log({
                              valid
                          })
                      } else {
                          console.log({
                              err: form3.errMsg
                          })
                      }
                  })
              } else {
                  console.log({
                      err: form2.errMsg
                  })
              }
          })
      } else {
          console.log({
              err: form1.errMsg
          })
      }
  })

  // 2. use async/await
  // 把callback包裝成promise,結合await同步寫法
  // 提高程式碼的可讀性
  let formSerialValidator = async function (forms) {
      let valid = true
      let errMsg = ''
      // 用for來模擬表單的線性呼叫 
      for (let form of forms) {
          // 利用await的同步寫法
          // 接收promise的resolve傳值,或reject的傳值
          // 保持 resolve/reject 傳值結構是一樣的: tuple [boolean, string]
          [valid, errMsg] = await promiseFactory(form)
          //   .catch(errMsg => {
          //       // 保持與resolve返回結果一致
          //       return [false, errMsg]
          //   })
          if (!valid) {
              break
          }
      }
      return [valid, errMsg]

      // 將回撥包裝成promise
      function promiseFactory(form) {
          return new Promise((resolve, reject) => {
              form.validator(valid => {
                  //   if (valid) {
                  resolve([valid, form.errMsg])
                  //   }
                  //   reject(form.errMsg)
              })
          })
      }
  }
  formSerialValidator([form1, form2, form3]).then(([valid, errMsg]) => {
      console.log({
          valid,
          errMsg
      })
  })


  // 3. promise + reduce
  // 利用then的鏈式呼叫
  // a().then(() => new Promise()).then(() => new Promise())  第一個then為reject時,整個鏈的狀態就是reject
  let reduceValidator = function (forms) {
      // 用reduce生成校驗的Promise chain

      // reduce的初始initValue得注意下,是Promise.resolve()
      // Promise.resolve()傳遞的是一個具體的值(undefined),所以狀態為fulfilled,可直接使用then呼叫
      // Promise then方法返回的promise,則等待該promise的返回
      return forms.reduce((promise, form) => {
          return promise.then($ => {
              return new Promise((resolve, reject) => {
                  form.validator(valid => {
                      console.log(valid)
                      // resolve 只能有一個引數
                      if (valid) {
                          resolve(true)
                      }
                      reject(form.errMsg)
                  })
              })
          })
      }, Promise.resolve())
  }
  reduceValidator([form1, form2, form3]).then(valid => {
      console.log({
          valid,
      })
  }).catch(errMsg => {
      console.log({
          errMsg
      })
  })

  // Promise then方法返回的promise,則等待該promise的返回
  Promise.resolve(new Promise((resolve, reject) => {
      resolve(1)
  })).then(res => {
      console.log(res)
  })
複製程式碼

表單驗證的yield寫法

 /// 傳統表單的校驗

 // 一般人的寫法
 // this.$message.error重複呼叫,不利於維護
 function validator1() {
     let msg = ''
     if (fd.displayPosition === null) {
         this.$message.error('請選擇傳送端')
         //  return false
     } else if (fd.type === null) {
         this.$message.error('請選擇通知方式')
         //  return false
     } else if (fd.peroid === undefined || JSON.stringify(fd.peroid) === '[null,null]') {
         this.$message.error('請選擇有效日期')
         //  return false
     } else if (self.isShowNum && fd.showNum === null) {
         msg = '請選擇彈出次數'
         //  return false
     }
     this.$message.error(msg)
     return true
 }

 // 二般人的寫法
 // 利用yield給暫停一下
 // generator函式呼叫後生成一個Iterator物件,返回的done為true時結束
 // Iterator: {
 //    next() {
 //         return {
 //             value: any,
 //             done: boolean
 //         }
 //     }
 // }
 //
 // for..of 實現了Iterator介面,可以呼叫generator函式的生成物件
 function validator2() {
     function* check() {
         if (fd.displayPosition === null) {
             yield '請選擇傳送端'
         }
         if (fd.type === null) {
             yield '請選擇通知方式'
         }
         if (fd.peroid === undefined || JSON.stringify(fd.peroid) === '[null,null]') {
             yield '請選擇有效日期'
         }
         if (self.isShowNum && fd.showNum === null) {
             yield '請選擇彈出次數'
         }
     }
     let iterator = check()
     for (let msg of iterator) {
         //  if (msg !== undefined) {
         this.$message.error(msg)
         return false
         //  }
     }
     return true
 }
複製程式碼

promise/async的錯誤處理

/// 1. promise內部的錯誤,不會中斷外部的執行
new Promise((resolve, reject) => {
    throw Error('promise')
    console.log(1)
})
console.log('after promise error')
// 可使用try-catch或Promise.prototype.catch處理
new Promise((resolve, reject) => {
    try {
        throw Error('promise')
    } catch (err) {
        console.log(err)
    }
})
console.log('after promise error')

// 與上面的捕獲錯誤的方法對比,throw Error後面的程式碼將無法執行
new Promise((resolve, reject) => {
        throw Error('promise')
        console.log('after inner error')
    })
    .catch(err => {
        console.log(err)
        // return 'form catch'
        throw Error('from catch')
    })
    .catch(res => {
        console.log(res)
    })

console.log('after promise error')


/// 2. async函式的錯誤處理
// 處理await返回的錯誤,promise不捕獲,則留給async函式處理
// promise前面加await與不加的區別
// 觀察下面3種情況:
// 1. 內部promise錯誤不處理
// 2. 內部promise加上catch
// 3. 內部promise接上await 
async function usePromiseCatch() {
    new Promise((resolve, reject) => {
        throw Error('throw')
        console.log(330)
    })
    // .catch(err => {
    //     console.log(err)
    // })
    console.log('continue')
}

usePromiseCatch().catch(err => {
    console.log('in async catch', err)
})
複製程式碼

相關文章