资讯专栏INFORMATION COLUMN

异步读取文件的几种姿势

chinafgj / 3451人阅读

摘要:臆想的针对读取到的内容进行操作,比如打印文件内容臆想中,读取文件是有返回值的,将返回值,即文件内容,赋给一个变量,然后决定对读取到的内容进行相应的操作,例如打印文件中的内容。

臆想的
let fs = require("fs")

function readFile(filename){
    ...
}

let content = readFile("config.js")
// 针对读取到的内容进行操作,比如打印文件内容
console.log(content)

臆想中,读取文件是有返回值的,将返回值,即文件内容,赋给一个变量,然后决定对读取到的内容进行相应的操作,例如打印文件中的内容。

简而言之,臆想中,读取文件,打印文件是相互分开的。

回调
let fs = require("fs")

function readFile(filename, callback){
    fs.readFile(filename, "utf8", (err, data) => {
        if(err){
            throw new err
        }
        callback(data)
    })
}

readFile("config.js", data => {
    console.log(data)
})

实际上,在经常使用的回调中,读取文件和针对文件内容相应的操作是在一起的,

你在要求读取文件的同时,还要说明获取文件内容后干嘛

这和习惯性思维,你先把文件内容给我,至于我怎么处理,稍后再说

Promise
let fs = require("fs")

function readFile(filename) {
    return new Promise(function (resolve) { // 这里的callback,是在run函数中传递的
        fs.readFile(filename, "utf8", (err, data) => {
            if (err) {
                reject(err)
            }
            resolve(data)
        })
    })
}

let content = readFile("config.js")
content.then(res => {
    console.log(res)
})
// 对比臆想中的
let content = readFile("config.js")
// 针对读取到的内容进行操作,比如打印文件内容
console.log(content)

使用Promise后,整个书写逻辑开始和臆想中的很接近了,读取文件和对文件内容的操作分开了

即通过使用Promise,可以将异步的操作和对异步结果的处理,分开

来实现一个简陋的假的Promise
let fs = require("fs")

function resolve(value) {
    let _self = this
    setTimeout(function () {
        _self.callbacks.forEach(function (callback) {
            callback(value);
        })
    }, 0)
    // 保证在resolve执行之前,then方法都已经注册
}

class FakePromise {

    constructor(fn) {
        // fn是个函数,里面包含异步,异步成功
        // Promise在new的过程中就已经开始执行异步代码
        // 异步代码执行完触发resolve,resolve作为异步代码的参数,它早已经实现好
        this.value = null
        this.callbacks = []
        fn(resolve.bind(this))
    }

    then(onFulfilled) {
        this.callbacks.push(onFulfilled)
        return this
    }
}

function readFile(filename) {
    return new FakePromise(function (resolve) {
        fs.readFile(filename, "utf8", (err, data) => {
            if (err) {
                return
            }
            resolve(data)
        })
    })
}

let content = readFile("config.js")
content.then(res => {
    console.log(res)
})

这样看来,Promise和发布-订阅模式有些相像。promise内部也有个事件队列,通过then注册事件,通过resolve触发。每个promise在创建的时候,就开始执行传递给它的函数,函数中会触发resolve,这个resolve就是去执行所有注册的事件。

当然,实际上promise比这强大的多,首先resolve执行所有注册的事件会保证滞后执行,避免还没通过then注册完事件,resolve就执行了

其次,在异步操作成功之后,通过then注册事件,可以立马执行,这就需要给promise添加状态机制

...
then(onFulfilled) {
        if (this.state === "pending") {
            this.callbacks.push(onFulfilled)
            return this
        }
        onFulfilled(value)
        return this
}
...

判断不是"pending",就立刻执行注册的函数

另外就是每个then()方法,都会返回一个新的promise

大概长这样

then(onFulfilled) {
    return new Promise(...)
}
参考资料

30分钟,让你彻底明白Promise原理

生成器
let fs = require("fs")

function run(taskDef){
    // 传入的taskDef是个生成器函数,执行后返回迭代器
    let task = taskDef()
    // 调用迭代器的next()方法,开始taskDef函数中的代码,直至遇到yield,并将yield的值赋予result
    let result = task.next()
    function step(){
      if(!result.done){
        if(typeof result.value === "function"){
          result.value((err, data)=>{
            if(err){
              result = task.throw(err)
              return
            }
            result = task.next(data)
            step()
          })
          // 这里的result.value(...){...}
          // 调用的是function(callback){fs.readFile(filename, "utf8", callback)}
        }else{
          result = task.next(data)
          step() // 判断任务是否执行完
        }
      }
    }
    step()
    // task.next(data) 继续执行生成器中的代码,并将值传回给触发这次next()的yield的等号左边的变量
}

function readFile(filename){
  return function (callback){ // 这里的callback,是在run函数中传递的
    fs.readFile(filename, "utf8", callback)
  }
}

run(function*(){
  let content = yield readFile("config.js") // 传递函数至run函数中,并由run传递参数调用
  // 对文件内容进行处理
  console.log(content)
})
// 对比臆想中的
let content = readFile("config.js")
// 针对读取到的内容进行操作,比如打印文件内容
console.log(content)

生成器函数可以停止函数执行,代码在yield readFile("copy.js")处暂停

异步任务的回调中调用迭代器的next()方法,使生成器函数中的代码继续执行,并通过next()方法传递参数回至生成器函数中,异步任务完成,返回值已经赋值给了content

Promise+generator
let fs = require("fs")

function run(taskDef){
    // 传入的taskDef是个生成器函数,执行后返回迭代器
    let task = taskDef()
    // 调用迭代器的next()方法,开始taskDef函数中的代码,直至遇到yield,并将yield的值赋予result
    let result = task.next()
    function step(){
      if(!result.done){
        let promise = Promise.resolve(result.value)
        promise.then(value => {
            result = task.next(value)
            step()
        }).catch(err => {
            result = task.throw(err)
            step()
        })
          
      }
    }
    step()
    // task.next(data) 继续执行生成器中的代码,并将值传回给触发这次next()的yield的等号左边的变量
}

function readFile(filename){
  return new Promise(function (resolve, reject){ // 这里的callback,是在run函数中传递的
    fs.readFile(filename, "utf8", (err, data) => {
        if(err){
            reject(err)
        }
        resolve(data)
    })
  })
}

run(function*(){
  let content = yield readFile("config.js") // 传递函数至run函数中,并由run传递参数调用
  // 对文件内容进行处理
  console.log(content)
})

console.log("先执行")
Async&await

是不是和promise+generator很像

let fs = require("fs")

function readFile(filename) {
    return new Promise(function (resolve) { // 这里的callback,是在run函数中传递的
        fs.readFile(filename, "utf8", (err, data) => {
            if (err) {
                reject(err)
            }
            resolve(data)
        })
    })
}
(async function test() {
    let content = await readFile("copy.js")
    console.log(content)
})()
参考资料

深入理解ES6

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92595.html

相关文章

  • Java日志正确使用姿势

    摘要:但是往往越简单的东西越容易让我们忽视,从而导致一些不该有的发生,作为一名严谨的程序员,怎么能让这种事情发生呢所以下面我们就来了解一下关于日志的那些正确使用姿势。级别表示出现了严重错误,程序将会中断执行。 前言 关于日志,在大家的印象中都是比较简单的,只须引入了相关依赖包,剩下的事情就是在项目中尽情的打印我们需要的信息了。但是往往越简单的东西越容易让我们忽视,从而导致一些不该有的bug发...

    UCloud 评论0 收藏0
  • 前端基础

    摘要:谈起闭包,它可是两个核心技术之一异步基于打造前端持续集成开发环境本文将以一个标准的项目为例,完全抛弃传统的前端项目开发部署方式,基于容器技术打造一个精简的前端持续集成的开发环境。 这一次,彻底弄懂 JavaScript 执行机制 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我。 不论你是javascript新手还是老鸟,不论是面试求职,还是日...

    graf 评论0 收藏0
  • 论JVM爆炸几种姿势及自救方法

    摘要:方法区溢出在的方法区中,它主要存放了类的信息,常量,静态变量等。运行结果简单解决思路一般来说此类问题多出现在存在递归的地方,要从代码里重新审视递归未结束的原因,若递归的方法没问题可以根据实际情况调整参数的大小。 前言 如今不管是在面试还是在我们的工作中,OOM总是不断的出现在我们的视野中,所以我们有必要去了解一下导致OOM的原因以及一些基本的调整方法,大家可以通过下面的事例来了解一下什...

    hyuan 评论0 收藏0
  • 聊聊Vue.js组件间通信几种姿势

    摘要:子组件向父组件通信方法一使用事件父组件向子组件传递事件方法,子组件通过触发事件,回调给父组件。非父子组件兄弟组件之间的数据传递非父子组件通信,官方推荐使用一个实例作为中央事件总线。 写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出。 文章的原地址:https://github.com/answ...

    Profeel 评论0 收藏0
  • JS常用几种异步流程控制

    摘要:虽然这个模式运行效果很不错,但是如果嵌套了太多的回调函数,就会陷入回调地狱。当需要跟踪多个回调函数的时候,回调函数的局限性就体现出来了,非常好的改进了这些情况。 JavaScript引擎是基于单线程 (Single-threaded) 事件循环的概念构建的,同一时刻只允许一个代码块在执行,所以需要跟踪即将运行的代码,那些代码被放在一个任务队列 (job queue) 中,每当一段代码准...

    Barry_Ng 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<