在前端开发中,我们经常需要用到异步编程,以确保我们的程序可以正确地运行。异步编程的方式有很多种,其中一种比较常用的方式是使用 Promise
。然而,在某些情况下,使用 Promise
会显得比较繁琐。ES8 引入了 async/await
语法来简化异步编程,使得我们可以像写同步代码一样写异步代码。
但是,并不是所有的浏览器都支持 ES8 的 async/await
语法。为了确保我们的代码可以在所有的浏览器上正常运行,我们可以使用一个转换器来将 async/await
语法转换成可以在所有浏览器上运行的代码。
在本文中,我们将手写一个实现 ES8 async/await
的转换器,并详细解释每个步骤的实现方法。
实现步骤
- 开始时,我们需要将
async/await
语法转换成生成器函数,并将其中的await
表达式转换成yield
表达式。
示例代码:
async function getUserInfo() { const userInfo = await fetch('/api/userinfo'); return userInfo; }
转换成:
function* getUserInfo() { const userInfo = yield fetch('/api/userinfo'); return userInfo; }
- 接下来,我们需要实现
run
函数,它的作用是执行生成器函数。我们需要创建一个迭代器对象,并使用next
方法执行生成器的第一个yield
表达式,然后根据表达式返回的值继续执行后面的代码。
示例代码:
// javascriptcn.com 代码示例 function run(genFunc) { const genObj = genFunc(); function step(val) { const genResult = genObj.next(val); if (!genResult.done) { genResult.value.then(step); } } step(); }
- 实现
step
函数,它的作用是根据返回值来决定向下执行还是等待。如果返回值是一个Promise
对象,我们需要等待这个对象返回结果后再继续执行。如果返回值不是Promise
对象,我们直接继续执行。
示例代码:
// javascriptcn.com 代码示例 function step(val) { const genResult = genObj.next(val); if (!genResult.done) { if (genResult.value instanceof Promise) { genResult.value.then(step); } else { step(genResult.value); } } }
- 实现
async/await
的转换器。我们遍历代码中的所有函数,并将其中的async
关键字去掉,将await
关键字转换成普通的函数调用。然后将其包装成一个立即执行的函数,并将其中所有用到的变量都传递进去。
示例代码:
// javascriptcn.com 代码示例 function asyncToGenerator(genFunc) { return function () { const genObj = genFunc.apply(this, arguments); return new Promise((resolve, reject) => { function step(val) { try { const genResult = genObj.next(val); if (genResult.done) { resolve(genResult.value); } else if (genResult.value instanceof Promise) { genResult.value.then(step).catch(reject); } else { Promise.resolve(genResult.value).then(step).catch(reject); } } catch (ex) { reject(ex); } } step(); }); }; } function transpile(code) { const ast = acorn.parse(code, { ecmaVersion: 8, sourceType: 'module' }); babel.transformFromAstSync(ast, null, { plugins: [ function ({ types: t }) { return { visitor: { FunctionDeclaration(path) { path.node.async = false; }, ArrowFunctionExpression(path) { path.node.async = false; }, FunctionExpression(path) { path.node.async = false; }, CallExpression(path) { if (path.node.callee.type === 'Identifier' && path.node.callee.name === 'fetch') { path.replaceWith( t.yieldExpression( t.callExpression(t.identifier('fetch'), [path.node.arguments[0]]) ) ); } }, AwaitExpression(path) { const parentFunc = path.findParent(path => path.isFunction()); const promiseCall = t.callExpression(t.memberExpression(path.node.argument, t.identifier('then')), [ t.functionExpression(null, [t.identifier('result')], t.blockStatement([ t.returnStatement( t.callExpression(t.memberExpression(parentFunc.node.id, t.identifier('next')), [ t.identifier('result') ]) ) ])) ]); path.replaceWith(t.yieldExpression(promiseCall)); } } }; } ] }); return asyncToGenerator(eval(code))(); }
示例代码
// javascriptcn.com 代码示例 async function getUserInfo() { const userInfo = await fetch('/api/userinfo'); return userInfo; } console.log('start:', new Date().getTime()); transpile(` async function main() { const userInfo = await getUserInfo(); console.log('userInfo:', userInfo); const posts = await fetch('/api/posts'); console.log('posts:', posts); return posts; } `).then(result => { console.log('end:', new Date().getTime()); console.log('result:', result); });
总结
本文手写了一个实现 ES8 async/await
的转换器,并详细解释了每个步骤的实现方法。虽然我们可以使用现有的工具来完成这个任务,但是手写一遍可以让我们更深入地理解异步编程和生成器函数的相关概念。同时,这个转换器对于我们在不支持 async/await
的浏览器上运行代码非常有用。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/653def527d4982a6eb78dc68