Promise、Generator与异步编程

一、异步任务运行

执行异步操作的传统方式是调用一个包含回调的函数,例如:

let fs = require("fs");
fs.readFile("config.json", function(err, contents) {
if (err) {
throw err;
}
doSomethingWith(contents);
console.log("Done");
});

   当你拥有数量少而有限的任务需要完成时,这么做很有效;然而当你需要嵌套回调函数,或者要按顺序处理一系列的异步任务时,此方式就会非常麻烦了。在这种场合下,生成器与 yield 会很有用。

二、生成器实现异步编程

  由于 yield 能停止运行,并在重新开始运行前等待 next() 方法被调用,你就可以在没有回调函数的情况下实现异步调用。

let fs = require("fs");
function run(taskDef) {
    // 创建迭代器,让它在别处可用
    let task = taskDef();
    // 开始任务
    let result = task.next();
    // 递归使用函数来保持对 next() 的调用
    function step() {
        // 如果还有更多要做的
        if (!result.done) {
            if (typeof result.value === "function") {
                result.value(function(err, data) {
                        if (err) {
                        result = task.throw(err);
                        return;
                       }
                        result = task.next(data);
                        step();
                });
            } else {
                    result = task.next(result.value);
                    step();
            }
        }
    }
    // 开始处理过程
    step();
 }
// 定义一个函数来配合任务运行器使用
function readFile(filename) {
    return function(callback) {
            fs.readFile(filename, callback);
    };
}
// 运行一个任务
run(function*() {
    let contents = yield readFile("config.json");
    doSomethingWith(contents);
    console.log("Done");
});

  此例执行了异步的 readFile() 操作,而在主要代码中并未暴露出任何回调函数。除了yield 之外,此代码看起来与同步代码并无二致。既然执行异步操作的函数都遵循了同一接口,你就可以用貌似同步的代码来书写处理逻辑。当然,这些范例中所使用的模式也有缺点:你无法完全确认一个能返回函数的函数是异步的。

三、Promise与Generator结合实现异步编程

  借助 Promise ,你可以确保每个异步操作都返回一个 Promise ,从而大幅度简化并一般化异步处理,通用接口也意味着你可以大大减少异步代码。

let fs = require("fs");
function run(taskDef) {
    // 创建迭代器
    let task = taskDef();
    // 启动任务
    let result = task.next();
    // 递归使用函数来进行迭代
    (function step() {
        // 如果还有更多要做的
        if (!result.done) {
            // 决议一个 Promise ,让任务处理变简单
            let promise = Promise.resolve(result.value);
            promise.then(function(value) {
                result = task.next(value);
                step();
            }).catch(function(error) {
                result = task.throw(error);
                step();
            });
        }
    }());
}
// 定义一个函数来配合任务运行器使用
function readFile(filename) {
    return new Promise(function(resolve, reject) {
        fs.readFile(filename, function(err, contents) {
            if (err) {
                reject(err);
            } else {
                resolve(contents);
            }
        });
    });
}
// 运行一个任务
run(function*() {
    let contents = yield readFile("config.json");//readFile执行异步操作
    doSomethingWith(contents);
    console.log("Done");
});

  调用 Promise.resolve() 只为预防未正确返回Promise 的函数(记住: Promise.resolve() 在被传入任意 Promise 时只会直接将其传递回来,而不是 Promise 的参数则会被包装为 Promise )。

  run() 函数能运行任意使用 yield 来实现异步代码的生成器,而不会将 Promise (或回调函数)暴露给开发者。事实上,由于函数调用后的返回值总是会被转换为一个 Promise ,该函数甚至允许返回 Promise 之外的类型。这意味着同步与异步方法在使用 yield 时都会正常工作,并且你永不需要检查返回值是否为一个 Promise 。唯一需要担心的是,要确保诸如 readFile() 的异步方法能返回一个正确标记其状态的Promise 。

  若只是单纯的使用Promise实现异步编程,只是回调函数的改进,使用then方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。并且原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆then,原来的语义变得很不清楚。所以将Promise和Generator结合使用,效果最佳。



阅读更多
版权声明: https://blog.csdn.net/qq_24839991/article/details/79970071
个人分类: 异步编程 ES6
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭