electronelectron-vue

Asynchronously read a file line by line with Electron-vue


I am developing with Electron-vue. I want to read a big text file provided by a user line by line. Also, I want to show a loading icon during the reading (which means the reading should be asynchronous).
Please note that the text file is encoded with shift-jis.

At first, I tried the following:
src/renderer/components/Import.vue

const dialog = require('electron').remote.dialog
export default {
  name: 'import',
  data () {
    return {
      loading: false
    }
  },
  methods: {
    mounted () {
      this.$refs.search.focus()
    },
    openDialog () {
      let filePath = dialog.showOpenDialog({
        properties: ['openFile'],
      })
      this.loading = true
      let that = this
      // Use the async function.
      this.getResultFileRead(that, filePath[0]).then(function (res) {
        // When the async function is completed, remove the loading icon.
        that.loading = false
        console.log(res)
      })
    },
    // Define async function here.
    async getResultFileRead (path) {
      return this.$electron.ipcRenderer.sendSync('readfile', path)
    }
  }
}

src/main/index.js

import fs from 'fs'
import readline from 'readline'
import iconv from 'iconv-lite'

ipcMain.on('readfile', (event, arg) => {
  // Using stream to decode shift-jis file.
  stream = fs.createReadStream(filePath).pipe(iconv.decodeStream('shift-jis'))
  let reader = readline.createInterface(stream, {})
  reader.on('line', line => {
     insertLine(line)
  })
  reader.on('close', () => {
    // do other stuff
  })
  event.returnValue = 'complete'
})

But these didn't work. When a file is provided, the file is read asynchronously, but the readfile function returns 'complete' as soon as file is provided (that is before completing the action), so the loading icon never appear.

How can I achieve both of "read file with shift-jis asynchronously" and "showing loading icon while the file is being read" on electron-vue? Please note that Electron-vue is seemingly using Node.js version 8.9.3.


Solution

  • I solved this problem as follows. The point is the resolve function in the close event.
    src/main/index.js

    import fs from 'fs'
    import readline from 'readline'
    import iconv from 'iconv-lite'
    
    ipcMain.on('readfile', async (event, arg) => {
        await readLines()
        event.sender.send('readfile-reply', 'complete')
    })
    
    function readLines() {
        return new Promise((resolve, reject) => {
            stream = fs.createReadStream(filePath).pipe(iconv.decodeStream('shift-jis'))
            let reader = readline.createInterface(stream, {})
            reader
                .on('line', line => {
                    insertLine(line)
                })
                .on('close', () => {
                    resolve() // "Resolve" in the close event.
                })
                .on('error', function(err) {
                    reject(err)
                })
        })
    }
    

    Then the function can be used asynchronously:
    src/renderer/components/Import.vue

    const dialog = require('electron').remote.dialog
    export default {
        name: 'import',
        data() {
            return {
                loading: false
            }
        },
        methods: {
            mounted() {
                this.$refs.search.focus()
            },
            openDialog() {
                let filePath = dialog.showOpenDialog({
                    properties: ['openFile'],
                })
                this.loading = true
                let that = this
                // Use the async function.
                this.asyncReadFile(that, filePath[0]).then(function(res) {
                    // When the async function is completed, remove the loading icon.
                    that.loading = false
                    console.log(res)
                    that.$electron.ipcRenderer.removeAllListeners('readfile-reply')
                })
            },
            asyncReadFile(that, path) {
                return new Promise(resolve => {
                    this.$electron.ipcRenderer.send('readfile', path)
                    this.$electron.ipcRenderer.on('readfile-reply', (event, result) => {
                        resolve(result)
                    })
                })
            }
        }
    }