【问题标题】:JavaScript generator-style asyncJavaScript 生成器风格的异步
【发布时间】:2014-03-20 20:17:58
【问题描述】:

据我了解,在 JS 中编写异步代码的未来风格是使用生成器而不是回调。至少,或者特别是。在 V8 / Nodejs 社区中。那正确吗? (但这可能是有争议的,这不是我的主要问题。)

要使用生成器编写异步代码,我找到了一些库:

它们看起来都有些相似,我不确定要使用哪一个(或者这是否重要)。 (但是,这可能再次值得商榷,也不是我在这里的主要问题——但我仍然会很高兴收到任何建议。)

(无论如何,我只使用纯 V8 - 如果这很重要。我没有使用 Nodejs,但我在自定义 C++ 应用程序中使用纯 V8。但是,我的代码中已经有一些节点样式元素,包括我的自定义require()。)

现在我有一些用回调样式编写的函数X,它本身使用回调参数调用其他异步函数,例如:

function X(v, callback) {
    return Y(onGotY);
    function onGotY(err, res) {
        if(err) return callback(err);
        return Z(onGotZ);
    }
    function onGotZ(err, res, resExtended) {
        if(err) return callback(err);
        return callback(null, v + res + resExtended);
    }
}

我想把X 变成一个生成器,例如我猜function* X(v) { ... }。那会是什么样子?

【问题讨论】:

    标签: javascript node.js asynchronous


    【解决方案1】:

    我使用了我自己的非常简单的库,它非常适合我的小型 V8 环境,并且由于 JS 调用堆栈保持不变,因此也很容易调试。不过,要使其与 Nodejs 或 Web 一起使用,它需要进行一些修改。

    这里的理由:

    对于调试,我们并不真正需要异步代码 - 我们希望在调试器中拥有易于理解的调用堆栈,尤其是。在节点检查器调试器中。 另请注意,到目前为止,所有异步代码都是完全人工的,我们完全同步地执行所有内容,在某种程度上通过 V8 微任务进行了模拟。 回调样式的异步代码在调用堆栈中已经很难理解。 生成器样式的异步代码在传统调试器中完全丢失了调用堆栈信息 - 其中包括与节点检查器一起使用的当前 Chrome Beta Developer Tools V8 调试器。这就是生成器(通常是协程)的本质。更高版本的调试器可能会处理这个问题,但今天并非如此。我们甚至需要一些特殊的 C++ 代码来获取信息。示例代码可以在这里找到: https://github.com/bjouhier/galaxy-stack/blob/master/src/galaxy-stack.cc

    所以如果我们想要有有用的调试器,今天我们不能使用生成器——至少不像通常那样使用。 我们仍然希望使用生成器风格的异步代码,因为与回调风格的代码相比,它使代码更具可读性。 我们引入了一个新函数async 来克服这个问题。想象一下以下回调风格的异步代码:

    function doSthX(a, b, callback) { ... }
    
    function doSthY(c, callback) {
        doSthX(c/2, 42, function(err, res) {
            if(err) return callback(err);
            callback(null, c + res);
        })
    }
    

    现在,与 async 函数和生成器样式代码相同的代码:

    function* doSthX(a, b) { ... }
    
    function* doSthY(c) {
        var res = yield async(doSthX(c/2, 42));
        return c + res;
    }
    

    我们可以提供两个版本的async(iter)

    1. 在堆栈顶部运行迭代器iter。这将保持一个健全的调用堆栈,一切都按顺序运行。
    2. 将迭代器交给一些较低的处理程序并使其真正异步。

    请注意,有一些值得注意的库可用于第二种方法:

    https://github.com/visionmedia/co http://taskjs.org/ https://github.com/creationix/gen-run https://github.com/bjouhier/galaxy

    目前,我们只实现第一种方法 - 使调试更容易。 如果我们曾经想要同时拥有两者,我们可以引入一个调试标志来通过这两种实现进行切换。

    global.async = async;
    function async(iter) {
        // must be an iterator
        assert(iter.next);
    
        var gotValue;
        var sendValue;
        while(true) {
            var next = iter.next(sendValue);
            gotValue = next.value;
    
            if(!next.done) {
                // We expect gotValue as a value returned from this function `async`.
                assert(gotValue.getResult);
                var res = gotValue.getResult();
                sendValue = res;
            }
    
            if(next.done) break;
        }
    
        return {
            getResult: function() {
                return gotValue;
            }
        };
    }
    
    
    // Like `async`, but wraps a callback-style function.
    global.async_call_cb = async_call_cb;
    function async_call_cb(f, thisArg /* , ... */) {
        assert(f.apply && f.call);
        var args = Array.prototype.slice.call(arguments, 2);
        return async((function*() {
            var gotCalled = false;
            var res;
    
            // This expects that the callback is run on top of the stack.
            // We will get this if we always use the wrapped enqueueMicrotask().
            // If we have to force this somehow else at some point, we could
            // call runMicrotasks() here - or some other waiter function,
            // to wait for our callback.
            args.push(callback);
            f.apply(thisArg, args);
    
            function callback(err, _res) {
                assert(!gotCalled);
                if(err) throw err;
                gotCalled = true;
                res = _res;
            }
    
            assert(gotCalled);
            return res;
        })());
    }
    
    
    
    // get the result synchronously from async
    global.sync_from_async = sync_from_async;
    function sync_from_async(s) {
        assert(s.getResult); // see async()
        return s.getResult();
    }
    
    
    // creates a node.js callback-style function from async
    global.callback_from_async = callback_from_async;
    function callback_from_async(s) {
        return function(callback) {
            var res;
            try { res = sync_from_async(s); }
            catch(err) {
                return callback(err);
            }
            return callback(null, res); 
        };
    }
    
    
    global.sync_get = sync_get;
    function sync_get(iter) {
        return sync_from_async(async(iter));
    }
    
    
    // this is like in gen-run.
    // it's supposed to run the main-function which is expected to be a generator.
    // f must be a generator
    // returns the result.
    global.run = run;
    function run(f) {
        return sync_get(f());
    }
    

    【讨论】:

    【解决方案2】:

    我假设“将 X 变成生成器”是指“将 X 变成可以生成的东西”,因为这就是这些库的工作方式。

    如果你使用co(我推荐),你需要让你的函数返回thunk,这是一个只接受回调的函数。很简单:

    function coX (v) {
        return function (cb) {
            X(v, cb);
        }
    }
    

    然后就是:

    co(function * () {
        yield coX('v');
    })();
    

    【讨论】:

      猜你喜欢
      • 2015-08-08
      • 2021-11-12
      • 2015-03-17
      • 2020-01-24
      • 2019-11-16
      • 2017-03-17
      • 1970-01-01
      • 2017-05-12
      相关资源
      最近更新 更多