企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
在JavaScript中做异步开发时,我们现在会毫不犹豫的使用`Async/Await`。不管是并发还是串行,`Async/Await`都能处理的很好,而且还保证了代码的可读性。但是,`Async/Await`是在ES6之后突然出现的, 这激发了我的好奇心,使得我想更进一步的了解它。本文假设读者是对Promise和Async有一定了解的,因此对基础部分不做过多的叙述。然后,我将通过以下三个部分来剖析`Async\Await`: * Generator函数 * 自动执行器 * 内置执行 `Async`函数的实现原理就是将Generator函数和自动执行器包装在一个函数里。 ### Generator函数 ### 基本用法 Generator函数是ES6提供的一种异步编程解决方案,它的调用方式与普通函数一样,也是在函数后面加上一对圆括号。不同的是,调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象(遍历器对象)。接着,必须调用遍历器对象的next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一条yield语句(或return语句)为止。 ~~~js function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator(); hw.next(); // { value: 'hello', done: false } hw.next(); // { value: 'world', done: false } hw.next(); // { value: 'ending', done: true } hw.next(); // { value: undefined, done: true } ~~~ ### 异步操作 Ajax是典型的异步操作,我们试着通过Generator函数部署Ajax,来达到同步表达的效果。 ~~~js function* main() { var result = yield request("url"); var res = JSON.parse(result); console.log(res.value) } function request(url) { makeAjaxCall(url, function(response) { it.next(response); }); } var it = main(); it.next(); ~~~ 这样似乎能满足我们的要求,但如果我们增加了请求的数量,那结果就不是我们期望的那样美好了:我们需要不断的调用next方法来逐个执行异步请求。如果Generator能够自动执行就好了。 ~~~js function main() { var result1 = yield request('url1'); var res1 = JSON.parse(result1); console.log(res1.value); var result2 = yield request('url2'); var res2 = JSON.parse(result2); console.log(res2.value) } function request(url) { makeAjaxCall(url, function(response) { it.next(response); }); } var it = main(); it.next(); it.next(); ~~~ ### 自动执行器 如果要自动执行,我们首先就会想到用循环来处理。 ~~~js function* gen() [ ...... } var g = gen(); var res = g.next(); while(!res.done) { console.log(res.value); res = g.next(); } ~~~ 上面的代码中,`gen`自动执行完所有步骤,但对于异步操作而言,必须保证前一步执行完才能执行后一步。根据经验,JavaScript中的回调函数和Promise刚好满足这个要求。 ### 基于Thunk(回调函数)的自动执行器 JavaScript中`Thunk`函数是用来将多参函数替换成只接受回调函数作为参数的单参函数。通过`Thunk`函数,我们可以将`next`方法作为回调函数传给 需要异步操作的方法,当异步操作完成时,调用`next`方法从而执行下一个操作,从而达到异步操作同步执行的效果。 ~~~js var fs = require('fs'); var thunk = function(fn) { return function() { var args = Array.prototype.slice.call(arguments); return function(callback) { args.push(callback); return fn.apply(this, args); } } }; var readFileThunk = thunk(fs.readFile); function run(fn) { var gen = fn(); function next(err, data) { var result = gen.next(data); if(result.done) return; result.value(next); } next(); } function* g() { var f1 = yield readFile('fileA'); var f2 = yield readFile('fileB'); // ... var fn = yield readFile('fileN'); } run(g); ~~~ ### 基于Promise对象的自动执行器 除了thunk之外,Promise也可以实现自动执行器。这其中的原理也是类似的,也是在确保异步操作完成的情况下调用`next`方法,从而执行下一次的异步操作。 ~~~js var fs = require('fs'); var readFile = function(fileName) { return new Promise(function(resolve, reject) { fs.readFile(fileName, function(resolve, reject) { if(error) return reject(error); resolve(data); }); }); }; var gen = function* () { var f1 = yield readFile('/etc/fstab'); var f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); } function run(gen) { var g = gen(); function next(data) { var result = g.next(data); if(result.done) return result.value; result.value.then(function(data) { next(data); }); } next(); } run(gen); ~~~ ### 内置执行 前面我们通过Generator函数写了读取文件的代码,接下来我们将它们改成async函数的形式。 ~~~js ...... var asyncReadFile = async function() { var f1 = await readFile('/etc/fstab'); var f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()) } ~~~ 通过比较就会发现,async函数就是将Generator函数的星号(\*)替换成async,将yield替换成await,然后内置了一个功能类似`run`的执行器。我们用代码模拟下一下这个执行器。 ~~~js async function fn(args) { //...... } // 等同于 function fn(args) { return spawn(function* () { //...... }); } function spawn(genF) { return new Promise(function(resolve, reject) { var gen = genF(); function step(nextF) { try { var next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resovle(next.value).then(function(v) { step(function(){ return gen.next(v); }); }, function(e){ step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); } ~~~ ### 总结 本文从Generator函数、自动执行器再到内置执行,分拆解析了`Async`的原理,希望能对大家有所帮助。