舉例對比說明promise和回調函數


通過不同的方式讀取在 files 文件夾下的三個文件來引出 promise 在處理異步時與回調函數相比的優勢,files 文件夾有三個文件 a.json,b.json,c.json。

// a.json
{
  "content": "this is a.json",
  "next": "b.json"
}
// b.json
{
  "content": "this is b.json",
  "next": "c.json"
}
// c.json
{
  "content": "this is c.json",
  "next": null
}

現在要依次讀取這三個文件,並且 b.json 的文件名要通過 a.json 文件中的 next 屬性獲得,c.json 的文件名要通過 b.json 的文件名獲得。

首先我們先來看一下讀取文件時輸出內容的格式

const fs = require('fs')
const path = require('path')

// 回調函數且不封裝為函數的方式
  const fullFileName = path.resolve(__dirname, 'files', 'a.json')
  fs.readFile(fullFileName, (err, data) => {
    console.log(data)
  })

從文件中直接讀取出來的是二進制的形式

data 是二進制形式

data二進制形式

data 轉換為字符串

console.log(data.toString());

data轉換為字符串

data 轉換為對象

console.log(JSON.parse(data.toString());

data轉換為對象

現在我們用最符合人思維方式的寫法來一次讀取三個文件的內容。

// 回調函數且不封裝為函數的方式
const fullFileName = path.resolve(__dirname, 'files', 'a.json')
// 讀取a.json
fs.readFile(fullFileName, (err, data) => {
  console.log(JSON.parse(data.toString()))
  const fileName = JSON.parse(data.toString()).next
  const fullFileName = path.resolve(__dirname, 'files', fileName)
  // 從a.json中獲得b.json文件名,然后讀取b.json
  fs.readFile(fullFileName, (err, data) => {
    console.log(JSON.parse(data.toString()))
    const fileName = JSON.parse(data.toString()).next
    const fullFileName = path.resolve(__dirname, 'files', fileName)
    // 從b.json中獲取c.json文件名,然后讀取c.json
    fs.readFile(fullFileName, (err, data) => {
      console.log(JSON.parse(data.toString()))
    })
  })
})

讀取結果:

promise示例1

上面這樣寫代碼復用性很低,我們可以考慮將讀取文件內容封裝為一個函數,這樣每次讀取文件內容時直接調用那個函數就可以了。

// 將讀取文件內容封裝成一個函數
function readFileContent(fileName) {
  fs.readFile(fileName, (err, data) => {
    console.log(JSON.parse(data.toString()))
  })
}
// 讀取a.json的內容
const fullFileName = path.resolve(__dirname, 'files', 'a.json')
readFileContent(fullFileName)

讀取結果:

promise示例2

如果我們想要完成連續讀取三個文件,並且下一個文件的文件名來自上一個文件,上面封裝的函數顯然是不能滿足要求的。

上面回調函數的內容是 console.log(JSON.parse(data.toString())),這樣寫死的顯然不能再讀取下一個文件,如果我們將 readFileContent 的第二個參數變成一個函數,然后在回調函數中調用執行,那么在這個函數中我們就可以再次讀取下一個文件。

// 封裝連續讀取文件的函數
function readFileContent(fileName, callback) {
  const fullFileName = path.resolve(__dirname, 'files', fileName)
  fs.readFile(fullFileName, (err, data) => {
    // 這里使用callback時需要傳遞一個參數,那么定義的callback函數也有一個參數
    callback(JSON.parse(data.toString()))
  })
}

const fileName = 'a.json'
readFileContent(fileName, aData => {
  console.log(aData);
  // 獲取b.json的名稱
  const fileName = aData.next;
  // 讀取b.json
  readFileContent(fileName, bData => {
    console.log(bData)
    // 獲取c.json的名稱
    const fileName = bData.next
    // 讀取c.json
    readFileContent(fileName, cData => {
      console.log(cData)
    })
  })
})

像上面這樣寫如果需要讀取的文件繼續增多,那么回調函數就會一直增加下去,呈現金字塔的形狀,函數中間嵌套着函數,導致代碼可讀性較低,這也就是經常說的回調地獄。

關於回調地獄推薦這篇博文,講的很清楚,回調地獄

解決回調地獄一種比較常用的方法就是使用 promise,關於 promise 的知識在這里就不多說了,現在利用 promise 讀取一個文件的內容。

const promise = new Promise((resolve, reject) => {
  const fullFileName = path.resolve(__dirname, 'files', 'a.json');
  fs.readFile(fullFileName, (err, data) => {
    if (err) {
      reject(err)
    } else {
      resolve(data)
    }
  })
})

promise.then((data) => {
  console.log(JSON.parse(data.toString()))
}, (err) => {
  console.log(err)
})

讀取結果

promise示例2

這樣寫很顯然不能完成多個文件的讀取,我們現在也考慮將其封裝為一個函數,如果讓這個函數返回一個 promise 那么調用一次就返回一個 promise,這樣就可以多次讀取文件了。

// 封裝函數利用promise讀取三個文件的內容
function readFileContent(fileName) {
  const fullFileName = path.resolve(__dirname, 'files', fileName)
  return new Promise((resolve, reject) => {
    fs.readFile(fullFileName, (err, data) => {
      if (err) {
        reject(err)
      } else {
        resolve(data)
      }
    })
  })
}

const fileName = 'a.json'
readFileContent(fileName).then((data) => {
  console.log(JSON.parse(data.toString()));
  const fileName = JSON.parse(data.toString()).next;
  return readFileContent(fileName)
}).then((data) => {
  console.log(JSON.parse(data.toString()));
  const fileName = JSON.parse(data.toString()).next;
  return readFileContent(fileName)
}).then((data) => {
  console.log(JSON.parse(data.toString()));
}) 

讀取結果:

promise示例4

重點在於第19行和23行的代碼,當在 then 中返回一個新的 promise 時,下一個 then 中的 data 就是這個新的 promise 中 resolve(data) 的參數 data,then 響應的是這個新的 promise。

可以看到當使用 promise 時,不會再出現函數嵌套的情況了,每個 then 都是一個異步操作,條理也比較清晰,因此 promise 也作為一種解決回調地獄比較常見的方式,解決回調地獄更多的方法可以參考上面推薦的那篇博客。

完,如有不恰當之處,還望告知,感謝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM