JavaScript发展这些年,关于异步场景解决方案的变化

JavaScript关于异步场景解决方案发展历程:回调函数 => Promise => Generator生成器函数 => async函数。

以文件读取示例:

阶段1,回调函数

const fs = require('fs')

const path = './package.json'

const readFile = (path, cb) => {
    fs.readFile(path, (err, data) => {
        if (err) cb(err)
        else cb(null, data)
    })
}

readFile(path, (err, data) => {
    if (!err) {
        console.log(JSON.parse(data))
    } else {
        console.log(err)
    }
})

优点:

  1. 解决了同步的问题

缺点: 

回调函数本身并没有问题,它的问题出现在多个回调函数嵌套。 

  1. 嵌套函数过多,回调中嵌着回调,造成回调地狱(callback hell)。导致调试困难,错误处理不易
  2. 嵌套函数存在一定耦合性,一旦有所改动,就会牵一发而动全身。
  3. 回调函数没有返回值(不要试图用return),仅仅被用来在函数内部执行某些动作。
readFile(path1, (err, data) => {
    if (!err) {
        console.log(JSON.parse(data))
        readFile(path2, (err, data) => {
            if (!err) {
                console.log(JSON.parse(data))
                readFile(path3, (err, data) => {
                    if (!err) {
                        console.log(JSON.parse(data))
                    } else {
                        console.log(err)
                    }
                })
            } else {
                console.log(err)
            }
        })
    } else {
        console.log(err)
    }
})

阶段2,Promise

const fs = require('fs')

const path = './package.json'

const readFileAsync = (path) => {
    return new Promise((resolve, reject) => {
        fs.readFile(path, (err, data) => {
            if (err) reject(err)
            else resolve(data)
        })
    })
}

readFileAsync(path)
    .then(data => {
        console.log(JSON.parse(data))
    })
    .catch(err => {
        console.log(err)
    })

优点:

  1. 解决了回调地狱的问题,使得原本多层级的嵌套代码,变成了链式调用,让代码更清晰。
  2. 异常捕获和处理异常简单多啦,只要在最后catch一下。
readFileAsync(path1)
    .then(data => {
        console.log(JSON.parse(data))
        return readFileAsync(path2)
    })
    .then(data => {
        console.log(JSON.parse(data))
        return readFileAsync(path3)
    })
    .then(data => {
        console.log(JSON.parse(data))
    })
    .catch(err => {
        console.log(err)
    })

缺点: 

  1. 原来的任务被Promise 包装了一下,不管什么操作,一眼看去都是一堆 then,原来的语义变得不清楚。

阶段3-基础,Generator生成器函数 + Promise

Generator生成器是ES6(ES2015)的新特性。当我们调用一个生成器函数的时候,它并不会立即执行, 而是需要我们手动的去执行迭代操作(next方法)。

也就是说,你调用生成器函数,它会返回给你一个迭代器。迭代器会遍历每个中断点。

通俗的讲,当你在执行一个函数的时候,你可以在某个点暂停函数的执行,并且做一些其他工作,然后再返回这个函数继续执行, 甚至是携带一些新的值,然后继续执行。

const util = require('util')
const fs = require('fs')

const path1 = './package1.json'
const path2 = './package2.json'
const path3 = './package3.json'

const readFileAsync = function *() {
    yield util.promisify(fs.readFile)(path1)
    yield util.promisify(fs.readFile)(path2)
    yield util.promisify(fs.readFile)(path3)
}

const g = readFileAsync()
g.next().value
    .then(data => {
        console.log(JSON.parse(data))
        return g.next().value
    })
    .then(data => {
        console.log(JSON.parse(data))
        return g.next().value
    })
    .then(data => {
        console.log(JSON.parse(data))
        return g.next().value
    })
    .catch(err => {
        console.log(err)
    })

 优点:

  1. Generator 函数将异步操作表示得很简洁

缺点:

  1. 流程管理不方便(何时执行第一阶段、何时执行第二阶段)。没有实现自动化的流程管理,需要手动调用next()。
  2. 一般的yield关键字后面会跟上Promise化的异步函数,因此要在then方法里面调用下一个next方法。于是在代码中任然有一堆then存在。

阶段3-进阶,Co库 + Generator生成器函数 + Promise

Co库是一个为Node.js和浏览器打造的基于Generator生成器函数的流程控制工具,借助于Promise,可以使用更加优雅的方式编写非阻塞代码。TJ大神所写。

注意:Co库使用时,yield后面一定要返回一个Promise对象

const co = require('co')
const util = require('util')
const fs = require('fs')

const path1 = './package1.json'
const path2 = './package2.json'
const path3 = './package3.json'

co(function *() {
    const data1 = yield util.promisify(fs.readFile)(path1)
    console.log(JSON.parse(data1))
    const data2 = yield util.promisify(fs.readFile)(path2)
    console.log(JSON.parse(data2))
    const data3 = yield util.promisify(fs.readFile)(path3)
    console.log(JSON.parse(data3))
    return 'test'
}).then(data => {
    console.log(data)
    // 输出 test
}).catch(err => {
    console.log(err)
})

优点:

  1. 实现自动化流程管理 
  2. 代码中没有一堆then啦

阶段4,Async/Await + Promise 统一世界

在ES7(还未正式标准化)中引入了Async函数的概念,使用async关键字,可以轻松地达成之前使用阶段3方法所做到的工作。8.x之后的node版本可以直接使用。

const util = require('util')
const fs = require('fs')

const path1 = './package1.json'
const path2 = './package2.json'
const path3 = './package3.json'

const readFileAsync = util.promisify(fs.readFile)

async function init() {
    const data1 = await readFileAsync(path1)
    console.log(JSON.parse(data1))
    const data2 = await readFileAsync(path2)
    console.log(JSON.parse(data2))
    const data3 = await readFileAsync(path3)
    console.log(JSON.parse(data3))
}

init()

优点:

  1. 代码清晰,不用像 Promise 写一大堆 then 链,解决了回调地狱的问题。

缺点:

  1. await 将异步代码改造成同步代码,如果多个异步操作没有前后依赖,这时使用 await 会降低性能。 

扩展阅读,关于Promise

Promise不是简单的语法糖,而是一种关于异步编程的规范,目的是将异步处理对象和处理规则进行规范化,为异步编程提供统一接口。

以下几段代码都是等价的,都是将普通回调函数Promise化。

1. 直接用ES6原生Promise对象

const fs = require('fs')

const readFileAsync = (path) => {
    return new Promise((resolve, reject) => {
        fs.readFile(path, (err, data) => {
            if (err) reject(err)
            else resolve(data)
        })
    })
}

2. 借助一些Promise库,一个流行的选择是使用 bluebird。这些库可能会提供比原生方案更多的功能,并且不局限于Promise/A+标准所规定的特性。 

const fs = require('fs')
const Promise = require('bluebird')

const readFileAsync = (path) => {
    return new Promise((resolve, reject) => {
        fs.readFile(path, (err, data) => {
            if (err) reject(err)
            else resolve(data)
        })
    })
}

 3. Node.js到8.x版本之后,可以使用Node.js里面的util模块提供的promisify方法轻易的去包装一个回调试的api,让他(这个回调式的api)可以直接支持Promise。

const util = require('util')
const fs = require('fs')

const readFileAsync = util.promisify(fs.readFile)

与君共勉:再牛逼的梦想,也抵不住傻逼般的坚持!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值