ES6之 Generator 函数的异步应用

本文介绍了ES6中的Generator函数在异步操作中的应用,包括如何通过yield暂停和next启动来实现协程,以及数据交换和错误处理。还探讨了Thunk函数作为参数求值策略,特别是用于解决Generator不适配异步操作的问题。最后,详细阐述了co模块的作用和原理,它是如何实现Generator函数的自动执行,并处理并发和Stream的。
摘要由CSDN通过智能技术生成

1. Generator函数

  • 协程实现:yield暂停,next启动
  • 数据交换和错误处理:yield和next参数,Generator内捕获外部错误
  • 异步任务封装
var fetch = require('node-fetch')

function* g() {
    let res = yield fetch('https://api.github.com/users/github')
    console.log(res)
}

var t = g()
var res = t.next()      //fetch返回Promise对象

res.value
    .then(data => data.json())
    .then(data => t.next(data))

2. Thunk函数

  • 参数求值策略:传值调用、传名调用
  • thunk函数的含义:使用临时函数实现传名调用
//fn为调用的函数,args为fn的参数列表,callback为回调函数
function Thunk(fn) {
    return function (...args) {
        console.log(this === global)        //true
        return function (callback) {
            return fn.call(this, ...args, callback)
        }
    }
}

function f(a, b) {
    b(a)
    console.log(this == obj)            //true
}

var ft = Thunk(f)
var obj = {
    fun: ft(1)         //1
}
obj.fun(console.warn)
  • Thunkify模块
var thunkify = require('thunkify')
var fs = require('fs')

//使用thunkify包装函数
thunkify(fs.readFile)('1.txt', 'utf-8')((err, str) => {
    console.log(str)        //我是1.txt(1.txt的内容)
})

//Thunkify源码
//fn为调用的函数,args为fn的参数列表,done为回调函数
function thunkify(fn) {
    return function () {
        var args = new Array(arguments.length);
        var ctx = this;

        for (var i = 0; i < args.length; ++i) {
            args[i] = arguments[i];
        }

        return function (done) {
            //回调函数只调用一次
            var called;

            args.push(function () {
                if (called) return;
                called = true;
                done.apply(null, arguments);
            });

            try {
                //apply(this,[...args,callback])类似call(this,...args,callback)
                fn.apply(ctx, args);
            } catch (err) {
                done(err);
            }
        }
    }
};

//f函数,最后一个参数为回调函数,其参数为前面参数的最大值
function f(...arr) {
    let callback = arr.pop()
    let max = Math.max(...arr)
    callback(max)
    callback(max)
    callback(max)
}

//改变f函数的调用方式,使其成为一个只接受回调函数作为参数的thunk函数
var ft = thunkify(f)
ft(1, 3, 5, 7, 11)(console.log)     //11 (只输出一次)
  • Generator 函数的流程管理
    • 解决问题:Generator不适合异步操作
//Generator 函数的流程管理
var fs = require('fs')
var thunkify = require('thunkify')
var readFileThunk = thunkify(fs.readFile)

var g = function* () {
    console.log(1, yield readFileThunk('1.txt', 'utf-8'))   //1 '我是1.txt'
    console.log(2, yield readFileThunk('2.txt', 'utf-8'))   //2 '我是2.txt'
}

var t = g()
t.next().value((err, data) => {         //启动遍历器
    t.next(data).value((err, data) => { //移动遍历器指针
        t.next(data)                    //移动遍历器指针
    })
})
  • Thunk 函数的自动流程管理
//Thunk 函数的自动流程管理(封装next调用)
var g = function* () {
    //依次执行异步操作
    console.log(1, yield readFileThunk('1.txt', 'utf-8'))   //1 '我是1.txt'
    console.log(2, yield readFileThunk('2.txt', 'utf-8'))   //2 '我是2.txt'   
    console.log(3, yield readFileThunk('3.txt', 'utf-8'))   //3 '我是3.txt'
    console.log(4, yield readFileThunk('4.txt', 'utf-8'))   //4 '我是4.txt'
}

//Generator函数自动执行器
function run(g) {
    var t = g()
    next()
    //Thunk回调函数
    function next(err, data) {
        let res = t.next(data)
        if (!res.done) {        //终止条件
            res.value(next)     //递归调用(内部把结果传给next函数调用)
        }
    }
}
run(g)

3. co 模块

  • 作用:用于Generator函数的自动执行
  • 原理:封装了两种自动执行器(Thunk函数和Promise对象)
    • 使用条件:yield后面只能是Thunk或Promise
//基于 Thunk 函数的自动执行
var g = function* () {
    //依次执行异步操作
    console.log('co', yield readFileThunk('1.txt', 'utf-8'))   //1 '我是1.txt'
    console.log('co', yield readFileThunk('2.txt', 'utf-8'))   //2 '我是2.txt'   
    console.log('co', yield readFileThunk('3.txt', 'utf-8'))   //3 '我是3.txt'
    console.log('co', yield readFileThunk('4.txt', 'utf-8'))   //4 '我是4.txt'
}
var co = require('co')
co(g).then(() => console.log('over'))


//基于 Promise 对象的自动执行
var fs = require('fs')
//Promise包装fs.readFile
function readFile1(fileName) {
    return new Promise(resolve => {
        fs.readFile(fileName, (err, data) => {
            resolve(data)
        })
    })
}

function* g1() {
    let r1 = yield readFile1('1.txt')
    let r2 = yield readFile1('2.txt')
    console.log(r1.toString(), r2.toString())   //我是1.txt 我是2.txt
}

//手动执行
var t1 = g1()
t1.next().value.then(data => {       //yield返回的value,是Promise对象
    t1.next(data).value.then(data => {
        t1.next(data)
    })
})

//自动执行
function run1(g) {
    let t = g()
    next()
    function next(data) {
        let res = t.next(data)
        if (res.done) {
            return res.value
        } else {
            res.value.then(data => {
                next(data)
            })
        }
    }
}
run1(g1)       //undefined
  • 源码
  • 处理并发
//处理并发
var co = require('co')
co(function* () {
    return yield [
        Promise.resolve(1),
        Promise.resolve(2)
    ]
}).then(data => console.log(data))      //[ 1, 2 ]
  • Stream
//Stream
var co = require('co')
var fs = require('fs')

var stream = fs.createReadStream('Generator应用1.js')
var count = 0

co(function* () {
    while (true) {
        const res = yield Promise.race([
            new Promise(resolve => stream.once('data', resolve)),
            new Promise(resolve => stream.once('end', resolve)),
            new Promise((res, rej) => stream.once('error', rej)),
        ])
        if (!res) {
            break
        }
        stream.removeAllListeners('data');
        stream.removeAllListeners('end');
        stream.removeAllListeners('error');
        count += (res.toString().match(/yield/g) || []).length;
    }
    console.log(count)
})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值