【真知拙見】回撥地獄和Promise

我你日哥發表於2019-02-14
非同步程式設計在JavaScript中非常重要,但是過多的非同步程式設計同時也帶來了回撥巢狀的問題。

什麼是回撥函式?

ajax(url, () => {});複製程式碼

以上程式碼就是一個回撥函式。一個函式作為引數需要依賴另一個函式執行呼叫。

但是回撥函式有一個致命弱點,容易出現回撥地獄(Callback hell)

什麼是回撥地獄?

let form = document.querySelector('form');
form.onsubmit = function (e) {
  var name = document.querySelector('input').value;
  $.ajax({
    url: "http://demo.com/submit",
    type:'POST',
    data: {name: name},
    success: function(res) {
        if (res.code === 2000) {
            var h1 = document.querySelector('h1').innerHTML;
            h1.innerHTML = res.data.name;
        }
    }
  });
}複製程式碼

像這樣,函式作為引數一層層的巢狀,使得程式碼塊看起來龐大、不清晰,不能一下子分清結構層級,這就稱為“回撥地獄”。

回撥地獄的根本問題與缺點:

  1. 巢狀函式存在耦合性,一旦有所改動,就會牽一髮而動全身
  2. 巢狀函式一多,就很難處理錯誤
  3. 回撥函式態使用try...catch...捕獲錯誤,不能直接return

怎麼解決回撥地獄?

主要原因是因為開發者的編碼習慣導致,我們可以通過以下方式來解決:

  • 保持簡短的程式碼風格,儘量使用命名函式,避免使用匿名函式

document.querySelector('form').onsubmit = onFormSubmit();

function onFormSubmit(e) {
  var name = document.querySelector('input').value;
  $.ajax({
    url: "http://demo.com/submit",
    type:'POST',
    data: {name: name},
    success: onSuccess(res)
  });
}

function onSuccess(res){
    if (res.code === 2000) {
        var h1 = document.querySelector('h1').innerHTML;
        h1.innerHTML = res.data.name;
    }
}複製程式碼

  • 模組化,拆分每一個獨立的功能函式,封裝、打包成一個單獨的js檔案,通過import匯入

// formHandler.js
module.exports.formSubmit = onFormSubmit;

function onFormSubmit(e) {
  var name = document.querySelector('input').value;
  $.ajax({
    url: "http://demo.com/submit",
    type:'POST',
    data: {name: name},
    success: onSuccess(res)
  });
}

function onSuccess(res){
    if (res.code === 2000) {
        var h1 = document.querySelector('h1').innerHTML;
        h1.innerHTML = res.data.name;
    }
}

// index.js
var formHandler = require('./formHandler');
document.querySelector('form').onsubmit = formHandler.formSubmit;複製程式碼
  • 處理每一個錯誤,按照標準規範編碼
  • Promise/Gengenerator/Async Function
除了常見的一種回撥函式作為非同步處理,還有promises,Generators,async是處理非同步處理的方式

Promise的特點是什麼?

Promise譯為承諾,承諾在以後、未來會有一個確切的回覆,並且該承諾擁有三種狀態:
  1. 等待中(pending)
  2. 完成了(resolved)
  3. 拒絕了(rejected)

這個承諾一旦狀態變更了以後,就不能再更改狀態了

當我們在構造Promise的時候,建構函式內部的程式碼是立即執行的

new Promise((resolve, reject) => {
  console.log('new Promise')
  resolve('success')
})
console.log('finish');

// new Promise
// finish複製程式碼

Promise有什麼缺點?

無法取消Promise,錯誤需要通過回撥函式來捕獲

什麼是Promise鏈?Promise建構函式執行和then函式執行有什麼區別?

Promise
實現了鏈式呼叫,也就是說每次呼叫 then 之後返回的都是一個 Promise,並且是一個全新的 Promise,原因也是因為狀態不可變。如果你在 then 中使用了return,那麼 return 的值會被 Promise.reslove() 包裝

Promise.resovle(1)
    .then(res => {
        console.log(res); // 1
        return 2; // 包裝成了 Promise.reslove(2)
    })
    .then(res => {
        console.log(res); // 2
    })
複製程式碼

Promise也解決了地獄回撥的問題:

// old
ajax(url, () => {
    // 處理邏輯
    ajax(url1, () => {
        // 處理邏輯
        ajax(url2, () => {
            // 處理邏輯
        })
    })
})

// new
ajax(url)
    .then(res => {
        console.log(res);
        return ajax(url1);
    })
    .then(res => {
        console.log(res);
        return ajax(url2);
    })
    .then(res => console.log(res));
複製程式碼

async和await

await 的同步只是在 async 函式裡同步,但並不會阻塞其他外部程式碼的執行。而這也是 await 必須放在 async 函式裡的原因

async 用於申明一個 function 是非同步的,而 await 用於等待一個非同步方法執行完成。


未完待續...


相關文章