资讯专栏INFORMATION COLUMN

js异步从入门到放弃(四)- Generator 封装异步任务

jimhs / 2260人阅读

摘要:总结本文简要介绍了函数的一些特性,重点在于说明如何使用函数对异步任务进行封装,从而能够让异步代码编写的更加清晰。

在之前的文章介绍了传统异步的实现方案,本文将介绍ES6中的一种全新的异步方案--Generator函数。

generator简介

简单介绍一下generator的原理和语法,(更详细内容请看ECMAScript 6 入门,本文只介绍和异步相关的核心内容)

基本语法

通过一个简单的例子来了解generator函数

function* MyGenerator() {
    yield "yield的含义是:执行此处时,暂停执行当前函数"
    yield "暂停之后的函数可以用next方法继续执行"
    return "遇到return之后会真正结束,done会变成true"
}

const gen = MyGenerator() //执行后返回的是一个指针,可以利用该指针执行next方法
console.log(gen.next()) // {value: "yield的含义是:执行此处时,暂停执行当前函数“, done: false}
console.log(gen.next())// {value: "暂停之后的函数可以用next方法继续执行", done: false}
console.log(gen.next())// {value: "遇到return之后会真正结束,done会变成true", done: true}
数据交互

数据交互指的是可以通过yield把当前执行的结果传出,也可以使用next把外部参数传入

    function* MyGenerator(){
        const result = yield "传出的数据"
        return result
    }
    const gen = MyGenerator()
    console.log(gen.next())// generatorAndAsync2.html:19 {value: "传出的数据", done: false}
    console.log(gen.next("传入的数据"))// {value: "传入的数据", done: true}

交互的参数类型当然也可以是函数

    function sayHi(){
        console.log("hi");
    }
    
    function* MyGenerator(){
        const result = yield sayHi()
        return 
    }
    
    const gen = MyGenerator()
    const result = gen.next()// {value: sayHi, done: false}
    result.value() // 正常输出"hi"

具备以上最核心的两个特性之后,generator就可以进行异步操作封装。

异步任务封装

首先,结合异步任务的特点以及前文提到的genrator函数的特性,提炼出使用generator封装异步操作的核心思路:

在异步任务执行时,使用yield交出执行权

在异步任务结束后,使用next交还执行权

起步

从一个最简单的例子开始:

// 1. 首先写一个异步任务,在一秒后返回特定字符串
function asyncTask(callback){
    setTimeout(()=>{
        callback("Hello Leo")
    }, 1000)
}

// 2. 接下来写出期望执行的顺序
function* runTask() {
    let text = yield asyncTask
    console.log(text) // 我们期望这里正常输出Hello Leo
}
// 3. 按照期望值执行函数
const gen = runTask()// 此时执行权已经交出
gen.next().value(function (text) {// 执行asyncTask并传入callback ,关键点在于在callback里调用next交还执行权
    gen.next(text)
}) 

首先,这段代码虽然很粗糙,但是已经反映了使用generator封装异步任务的核心思想。最直观的受益就是:runTask的内容看起来很像同步代码,条理清晰,很适合阅读。

但是上面第3部分关于执行的代码很不灵活,我们不能每次都这么写一段,因此接下来的目标就是实现一个任务执行器

自动任务执行器

同样的,先思考核心的思路:要想让某个generator函数自动执行,无非就是一个while循环:

1. 如果当前yield返回值的done属性为true,说明任务已经执行完成;
2. 如果当前yield返回值的done属性为false,说明任务还未执行完成,则继续执行next,同时要注意参数传递

根据分析实现以下的执行器:

function autoExecute(task) {
    const gen = task()
    let result = gen.next()
    while(true){
        if(result.done){
            break // 执行结束
            return 
        }
        console.log(result.value)//为了便于观察 我们加上console.log
        result = gen.next(result.value) // 每次都应该重写result 获取最新结果
    }
}

function* simpleTask(){
    yield 1
    yield 2
    yield 3
    return 
}

autoExecute(simpleTask)// 测试我们写的自动执行器 能够正确输出123

上面的执行器已经有了雏形,但是对于前面例子中,result.value为函数的情况还没有处理,因此还需要稍微补充:

function isFunction(source){
     return Object.prototype.toString.call(source) === "[object Function]"
}

function autoExecute(task) {
    const gen = task()
    let result = gen.next()
    let isRuningAsync = false // 由于加入了异步处理,所以要增加一个标志位避免重复进入循环体
    while (!isRuningAsync) {
        if (result.done) {
            return
        }
        console.log(result.value)

        /* start 补充的处理函数 */
        if (isFunction(result.value)) {
            isRuningAsync = true
            const callback = (arg) => {
                result = gen.next(arg) // 核心代码
                isRuningAsync = false
            }
            result.value(callback)
            /* end 补充的处理函数 */
        } else {
            result = gen.next(result.value)
        }
    }
}
autoExecute(runTask) // 试着用这个自动执行器执行之前的异步任务

上面这个自动执行器,就完成了generator对异步任务的封装。

总结

本文简要介绍了generator函数的一些特性,重点在于说明如何使用generator函数对异步任务进行封装,从而能够让异步代码编写的更加清晰。

再次强调:用generator函数对异步任务封装的思想是很明确的--控制 Generator 函数的流程,在适当的时机接收和交还程序的执行权,但是具体的实现方式并不唯一,例如本文用的是最简单直接的回调函数方式,在阮一峰老师的《es6入门》教程里,也有使用thunk思路来讲解的部分,可以自行查阅。

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

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

相关文章

  • js异步入门放弃(实践篇) — 常见写法&面试题解析

    摘要:前文该系列下的前几篇文章分别对不同的几种异步方案原理进行解析,本文将介绍一些实际场景和一些常见的面试题。流程调度里比较常见的一种错误是看似串行的写法,可以感受一下这个例子判断以下几种写法的输出结果辨别输出顺序这类题目一般出现在面试题里。 前文 该系列下的前几篇文章分别对不同的几种异步方案原理进行解析,本文将介绍一些实际场景和一些常见的面试题。(积累不太够,后面想到再补) 正文 流程调度...

    Awbeci 评论0 收藏0
  • JavaScript异步编程的终极演变

    摘要:在诞生以前,异步编程的方式大概有下面四种回调函数事件监听发布订阅对象将异步编程带入了一个全新的阶段,中的函数更是给出了异步编程的终极解决方案。这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的。 写在前面 有一个有趣的问题: 为什么Node.js约定回调函数的第一个参数必须是错误对象err(如果没有错误,该参数就是null)? 原因是执行回调函...

    whjin 评论0 收藏0
  • 谈谈ES6前后的异步编程

    摘要:回调函数这是异步编程最基本的方法。对象对象是工作组提出的一种规范,目的是为异步编程提供统一接口。诞生后,出现了函数,它将异步编程带入了一个全新的阶段。 更多详情点击http://blog.zhangbing.club/Ja... Javascript 语言的执行环境是单线程的,如果没有异步编程,根本没法用,非卡死不可。 为了解决这个问题,Javascript语言将任务的执行模式分成两种...

    fizz 评论0 收藏0
  • 我了解的JavaScript异步编程

    摘要:接下来我们看下三类异步编程的实现。事件监听事件发布订阅事件监听是一种非常常见的异步编程模式,它是一种典型的逻辑分离方式,对代码解耦很有用处。 一、 一道面试题 前段时间面试,考察比较多的是js异步编程方面的相关知识点,如今,正好轮到自己分享技术,所以想把js异步编程学习下,做个总结。下面这个demo 概括了大多数面试过程中遇到的问题: for(var i = 0; i < 3; i++...

    RichardXG 评论0 收藏0
  • ES6(一) —— 异步编程解决办法[回调函数promise,generator,async]

    摘要:回调函数这是最原始的一种异步解决方法。从的对象演化而来对象是提出的一种对异步编程的解决方案,但它不是新的语法,而是一种新的写法,允许将回调函数的嵌套改成链式调用。 一、前言 异步编程对JavaScript来说非常重要,因为JavaScript的语言环境是单线程的,如果没有异步编程将变得非常可怕,估计根本无法使用。这篇文章就来总结一下从最原始的回调函数到现在的ES6、ES7的新方法。 文...

    greatwhole 评论0 收藏0

发表评论

0条评论

jimhs

|高级讲师

TA的文章

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