20.|20. async 函数

仅为方便个人查询使用
来源:http://es6.ruanyifeng.com/#docs/async
含义 ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
前文有一个 Generator 函数,依次读取两个文件。
const fs = require('fs'); const readFile = function (fileName) { return new Promise(function (resolve, reject) { fs.readFile(fileName, function(error, data) { if (error) return reject(error); resolve(data); }); }); }; const gen = function* () { const f1 = yield readFile('/etc/fstab'); const f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };

上面代码的函数gen可以写成async函数,就是下面这样。
const asyncReadFile = async function () { const f1 = await readFile('/etc/fstab'); const f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };

一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
async函数对 Generator 函数的改进,体现在以下四点。
(1)内置执行器。
Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
asyncReadFile();

上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next方法,或者用co模块,才能真正执行,得到最后结果。
(2)更好的语义。
asyncawait,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
(3)更广的适用性。
co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
(4)返回值是 Promise。
async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。
进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。
基本用法 async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
下面是一个例子。
async function getStockPriceByName(name) { const symbol = await getStockSymbol(name); const stockPrice = await getStockPrice(symbol); return stockPrice; }getStockPriceByName('goog').then(function (result) { console.log(result); });

上面代码是一个获取股票报价的函数,函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。
下面是另一个例子,指定多少毫秒后输出一个值。
function timeout(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); }async function asyncPrint(value, ms) { await timeout(ms); console.log(value); }asyncPrint('hello world', 50);

上面代码指定 50 毫秒以后,输出hello world
由于async函数返回的是 Promise 对象,可以作为await命令的参数。所以,上面的例子也可以写成下面的形式。
async function timeout(ms) { await new Promise((resolve) => { setTimeout(resolve, ms); }); }async function asyncPrint(value, ms) { await timeout(ms); console.log(value); }asyncPrint('hello world', 50);

async 函数有多种使用形式。
// 函数声明 async function foo() {}// 函数表达式 const foo = async function () {}; // 对象的方法 let obj = { async foo() {} }; obj.foo().then(...)// Class 的方法 class Storage { constructor() { this.cachePromise = caches.open('avatars'); }async getAvatar(name) { const cache = await this.cachePromise; return cache.match(`/avatars/${name}.jpg`); } }const storage = new Storage(); storage.getAvatar('jake').then(…); // 箭头函数 const foo = async () => {};

语法 async函数的语法规则总体上比较简单,难点是错误处理机制。
返回 Promise 对象
async函数返回一个 Promise 对象。
async函数内部return语句返回的值,会成为then方法回调函数的参数。
async function f() { return 'hello world'; }f().then(v => console.log(v)) // "hello world"

上面代码中,函数f内部return命令返回的值,会被then方法回调函数接收到。
async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
async function f() { throw new Error('出错了'); }f().then( v => console.log(v), e => console.log(e) ) // Error: 出错了

Promise 对象的状态变化
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
下面是一个例子。
async function getTitle(url) { let response = await fetch(url); let html = await response.text(); return html.match(/([\s\S]+)<\/title>/i)[1]; } getTitle('https://tc39.github.io/ecma262/').then(console.log) // "ECMAScript 2017 Language Specification" </code></blockquote><br /> 上面代码中,函数<code>getTitle</code>内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,才会执行<code>then</code>方法里面的<code>console.log</code>。<br /> await 命令<br /> 正常情况下,<code>await</code>命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。<br /> <blockquote><code>async function f() { // 等同于 // return 123; return await 123; }f().then(v => console.log(v)) // 123 </code></blockquote><br /> 上面代码中,<code>await</code>命令的参数是数值<code>123</code>,这时等同于<code>return 123</code>。<br /> 另一种情况是,<code>await</code>命令后面是一个<code>thenable</code>对象(即定义<code>then</code>方法的对象),那么<code>await</code>会将其等同于 Promise 对象。<br /> <blockquote><code>class Sleep { constructor(timeout) { this.timeout = timeout; } then(resolve, reject) { const startTime = Date.now(); setTimeout( () => resolve(Date.now() - startTime), this.timeout ); } }(async () => { const sleepTime = await new Sleep(1000); console.log(sleepTime); })(); // 1000 </code></blockquote><br /> 上面代码中,<code>await</code>命令后面是一个<code>Sleep</code>对象的实例。这个实例不是 Promise 对象,但是因为定义了<code>then</code>方法,<code>await</code>会将其视为<code>Promise</code>处理。<br /> 这个例子还演示了如何实现休眠效果。JavaScript 一直没有休眠的语法,但是借助<code>await</code>命令就可以让程序停顿指定的时间。下面给出了一个简化的<code>sleep</code>实现。<br /> <blockquote><code>function sleep(interval) { return new Promise(resolve => { setTimeout(resolve, interval); }) }// 用法 async function one2FiveInAsync() { for(let i = 1; i <= 5; i++) { console.log(i); await sleep(1000); } }one2FiveInAsync(); </code></blockquote><br /> <code>await</code>命令后面的 Promise 对象如果变为<code>reject</code>状态,则<code>reject</code>的参数会被<code>catch</code>方法的回调函数接收到。<br /> <blockquote><code>async function f() { await Promise.reject('出错了'); }f() .then(v => console.log(v)) .catch(e => console.log(e)) // 出错了 </code></blockquote><br /> 注意,上面代码中,<code>await</code>语句前面没有<code>return</code>,但是<code>reject</code>方法的参数依然传入了<code>catch</code>方法的回调函数。这里如果在<code>await</code>前面加上<code>return</code>,效果是一样的。<br /> 任何一个<code>await</code>语句后面的 Promise 对象变为<code>reject</code>状态,那么整个<code>async</code>函数都会中断执行。<br /> <blockquote><code>async function f() { await Promise.reject('出错了'); await Promise.resolve('hello world'); // 不会执行 } </code></blockquote><br /> 上面代码中,第二个<code>await</code>语句是不会执行的,因为第一个<code>await</code>语句状态变成了<code>reject</code>。<br /> 有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个<code>await</code>放在<code>try...catch</code>结构里面,这样不管这个异步操作是否成功,第二个<code>await</code>都会执行。<br /> <blockquote><code>async function f() { try { await Promise.reject('出错了'); } catch(e) { } return await Promise.resolve('hello world'); }f() .then(v => console.log(v)) // hello world </code></blockquote><br /> 另一种方法是<code>await</code>后面的 Promise 对象再跟一个<code>catch</code>方法,处理前面可能出现的错误。<br /> <blockquote><code>async function f() { await Promise.reject('出错了') .catch(e => console.log(e)); return await Promise.resolve('hello world'); }f() .then(v => console.log(v)) // 出错了 // hello world </code></blockquote><br /> 错误处理<br /> 如果<code>await</code>后面的异步操作出错,那么等同于<code>async</code>函数返回的 Promise 对象被<code>reject</code>。<br /> <blockquote><code>async function f() { await new Promise(function (resolve, reject) { throw new Error('出错了'); }); }f() .then(v => console.log(v)) .catch(e => console.log(e)) // Error:出错了 </code></blockquote><br /> 上面代码中,<code>async</code>函数<code>f</code>执行后,<code>await</code>后面的 Promise 对象会抛出一个错误对象,导致<code>catch</code>方法的回调函数被调用,它的参数就是抛出的错误对象。具体的执行机制,可以参考后文的“async 函数的实现原理”。<br /> 防止出错的方法,也是将其放在<code>try...catch</code>代码块之中。<br /> <blockquote><code>async function f() { try { await new Promise(function (resolve, reject) { throw new Error('出错了'); }); } catch(e) { } return await('hello world'); } </code></blockquote><br /> 如果有多个<code>await</code>命令,可以统一放在<code>try...catch</code>结构中。<br /> <blockquote><code>async function main() { try { const val1 = await firstStep(); const val2 = await secondStep(val1); const val3 = await thirdStep(val1, val2); console.log('Final: ', val3); } catch (err) { console.error(err); } } </code></blockquote><br /> 下面的例子使用<code>try...catch</code>结构,实现多次重复尝试。<br /> <blockquote><code>const superagent = require('superagent'); const NUM_RETRIES = 3; async function test() { let i; for (i = 0; i < NUM_RETRIES; ++i) { try { await superagent.get('http://google.com/this-throws-an-error'); break; } catch(err) {} } console.log(i); // 3 }test(); </code></blockquote><br /> 上面代码中,如果<code>await</code>操作成功,就会使用<code>break</code>语句退出循环;如果失败,会被<code>catch</code>语句捕捉,然后进入下一轮循环。<br /> 使用注意点<br /> 第一点,前面已经说过,<code>await</code>命令后面的<code>Promise</code>对象,运行结果可能是<code>rejected</code>,所以最好把<code>await</code>命令放在<code>try...catch</code>代码块中。<br /> <blockquote><code>async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); } }// 另一种写法async function myFunction() { await somethingThatReturnsAPromise() .catch(function (err) { console.log(err); }); } </code></blockquote><br /> 第二点,多个<code>await</code>命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。<br /> <blockquote><code>let foo = await getFoo(); let bar = await getBar(); </code></blockquote><br /> 上面代码中,<code>getFoo</code>和<code>getBar</code>是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有<code>getFoo</code>完成以后,才会执行<code>getBar</code>,完全可以让它们同时触发。<br /> <blockquote><code>// 写法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 写法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise; </code></blockquote><br /> 上面两种写法,<code>getFoo</code>和<code>getBar</code>都是同时触发,这样就会缩短程序的执行时间。<br /> 第三点,<code>await</code>命令只能用在<code>async</code>函数之中,如果用在普通函数,就会报错。<br /> <blockquote><code>async function dbFuc(db) { let docs = [{}, {}, {}]; // 报错 docs.forEach(function (doc) { await db.post(doc); }); } </code></blockquote><br /> 上面代码会报错,因为<code>await</code>用在普通函数之中了。但是,如果将<code>forEach</code>方法的参数改成<code>async</code>函数,也有问题。<br /> <blockquote><code>function dbFuc(db) { //这里不需要 async let docs = [{}, {}, {}]; // 可能得到错误结果 docs.forEach(async function (doc) { await db.post(doc); }); } </code></blockquote><br /> 上面代码可能不会正常工作,原因是这时三个<code>db.post</code>操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用<code>for</code>循环。<br /> <blockquote><code>async function dbFuc(db) { let docs = [{}, {}, {}]; for (let doc of docs) { await db.post(doc); } } </code></blockquote><br /> 如果确实希望多个请求并发执行,可以使用<code>Promise.all</code>方法。当三个请求都会<code>resolved</code>时,下面两种写法效果相同。<br /> <blockquote><code>async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = await Promise.all(promises); console.log(results); }// 或者使用下面的写法async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc)); let results = []; for (let promise of promises) { results.push(await promise); } console.log(results); } </code></blockquote><br /> 第四点,async 函数可以保留运行堆栈。<br /> <blockquote><code>const a = () => { b().then(() => c()); }; </code></blockquote><br /> 上面代码中,函数<code>a</code>内部运行了一个异步任务<code>b()</code>。当<code>b()</code>运行的时候,函数<code>a()</code>不会中断,而是继续执行。等到<code>b()</code>运行结束,可能<code>a()</code>早就运行结束了,<code>b()</code>所在的上下文环境已经消失了。如果<code>b()</code>或<code>c()</code>报错,错误堆栈将不包括<code>a()</code>。<br /> 现在将这个例子改成<code>async</code>函数。<br /> <blockquote><code>const a = async () => { await b(); c(); }; </code></blockquote><br /> 上面代码中,<code>b()</code>运行的时候,<code>a()</code>是暂停执行,上下文环境都保存着。一旦<code>b()</code>或<code>c()</code>报错,错误堆栈将包括<code>a()</code>。<br /> async 函数的实现原理 async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。<br /> <blockquote><code>async function fn(args) { // ... }// 等同于function fn(args) { return spawn(function* () { // ... }); } </code></blockquote><br /> 所有的<code>async</code>函数都可以写成上面的第二种形式,其中的<code>spawn</code>函数就是自动执行器。<br /> 下面给出<code>spawn</code>函数的实现,基本就是前文自动执行器的翻版。<br /> <blockquote><code>function spawn(genF) { return new Promise(function(resolve, reject) { const gen = genF(); function step(nextF) { let next; try { next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(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); }); }); } </code></blockquote><br /> 与其他异步处理方法的比较 <strong>【20.|20. async 函数】</strong>我们通过一个例子,来看 async 函数与 Promise、Generator 函数的比较。<br /> 假定某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值。<br /> 首先是 Promise 的写法。<br /> <blockquote><code>function chainAnimationsPromise(elem, animations) {// 变量ret用来保存上一个动画的返回值 let ret = null; // 新建一个空的Promise let p = Promise.resolve(); // 使用then方法,添加所有动画 for(let anim of animations) { p = p.then(function(val) { ret = val; return anim(elem); }); }// 返回一个部署了错误捕捉机制的Promise return p.catch(function(e) { /* 忽略错误,继续执行 */ }).then(function() { return ret; }); } </code></blockquote><br /> 虽然 Promise 的写法比回调函数的写法大大改进,但是一眼看上去,代码完全都是 Promise 的 API(<code>then</code>、<code>catch</code>等等),操作本身的语义反而不容易看出来。<br /> 接着是 Generator 函数的写法。<br /> <blockquote><code>function chainAnimationsGenerator(elem, animations) {return spawn(function*() { let ret = null; try { for(let anim of animations) { ret = yield anim(elem); } } catch(e) { /* 忽略错误,继续执行 */ } return ret; }); } </code></blockquote><br /> 上面代码使用 Generator 函数遍历了每个动画,语义比 Promise 写法更清晰,用户定义的操作全部都出现在<code>spawn</code>函数的内部。这个写法的问题在于,必须有一个任务运行器,自动执行 Generator 函数,上面代码的<code>spawn</code>函数就是自动执行器,它返回一个 Promise 对象,而且必须保证<code>yield</code>语句后面的表达式,必须返回一个 Promise。<br /> 最后是 async 函数的写法。<br /> <blockquote><code>async function chainAnimationsAsync(elem, animations) { let ret = null; try { for(let anim of animations) { ret = await anim(elem); } } catch(e) { /* 忽略错误,继续执行 */ } return ret; } </code></blockquote><br /> 可以看到 Async 函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。它将 Generator 写法中的自动执行器,改在语言层面提供,不暴露给用户,因此代码量最少。如果使用 Generator 写法,自动执行器需要用户自己提供。<br /> 实例:按顺序完成异步操作 实际开发中,经常遇到一组异步操作,需要按照顺序完成。比如,依次远程读取一组 URL,然后按照读取的顺序输出结果。<br /> Promise 的写法如下。<br /> <blockquote><code>function logInOrder(urls) { // 远程读取所有URL const textPromises = urls.map(url => { return fetch(url).then(response => response.text()); }); // 按次序输出 textPromises.reduce((chain, textPromise) => { return chain.then(() => textPromise) .then(text => console.log(text)); }, Promise.resolve()); } </code></blockquote><br /> 上面代码使用<code>fetch</code>方法,同时远程读取一组 URL。每个<code>fetch</code>操作都返回一个 Promise 对象,放入<code>textPromises</code>数组。然后,<code>reduce</code>方法依次处理每个 Promise 对象,然后使用<code>then</code>,将所有 Promise 对象连起来,因此就可以依次输出结果。<br /> 这种写法不太直观,可读性比较差。下面是 async 函数实现。<br /> <blockquote><code>async function logInOrder(urls) { for (const url of urls) { const response = await fetch(url); console.log(await response.text()); } } </code></blockquote><br /> 上面代码确实大大简化,问题是所有远程操作都是继发。只有前一个 URL 返回结果,才会去读取下一个 URL,这样做效率很差,非常浪费时间。我们需要的是并发发出远程请求。<br /> <blockquote><code>async function logInOrder(urls) { // 并发读取远程URL const textPromises = urls.map(async url => { const response = await fetch(url); return response.text(); }); // 按次序输出 for (const textPromise of textPromises) { console.log(await textPromise); } } </code></blockquote><br /> 上面代码中,虽然<code>map</code>方法的参数是<code>async</code>函数,但它是并发执行的,因为只有<code>async</code>函数内部是继发执行,外部不受影响。后面的<code>for..of</code>循环内部使用了<code>await</code>,因此实现了按顺序输出。<br /> 顶层 await 根据语法规格,<code>await</code>命令只能出现在 async 函数内部,否则都会报错。<br /> <blockquote><code>// 报错 const data = https://www.it610.com/article/await fetch('https://api.example.com'); </code></blockquote><br /> 上面代码中,<code>await</code>命令独立使用,没有放在 async 函数里面,就会报错。<br /> 目前,有一个语法提案,允许在模块的顶层独立使用<code>await</code>命令。这个提案的目的,是借用<code>await</code>解决模块异步加载的问题。<br /> <blockquote><code>// awaiting.js let output; async function main() { const dynamic = await import(someMission); const data = https://www.it610.com/article/await fetch(url); output = someProcess(dynamic.default, data); } main(); export { output }; </code></blockquote><br /> 上面代码中,模块<code>awaiting.js</code>的输出值<code>output</code>,取决于异步操作。我们把异步操作包装在一个 async 函数里面,然后调用这个函数,只有等里面的异步操作都执行,变量<code>output</code>才会有值,否则就返回<code>undefined</code>。<br /> 上面的代码也可以写成立即执行函数的形式。<br /> <blockquote><code>// awaiting.js let output; (async function main() { const dynamic = await import(someMission); const data = https://www.it610.com/article/await fetch(url); output = someProcess(dynamic.default, data); })(); export { output }; </code></blockquote><br /> 下面是加载这个模块的写法。<br /> <blockquote><code>// usage.js import { output } from "./awaiting.js"; function outputPlusValue(value) { return output + value }console.log(outputPlusValue(100)); setTimeout(() => console.log(outputPlusValue(100), 1000); </code></blockquote><br /> 上面代码中,<code>outputPlusValue()</code>的执行结果,完全取决于执行的时间。如果<code>awaiting.js</code>里面的异步操作没执行完,加载进来的<code>output</code>的值就是<code>undefined</code>。<br /> 目前的解决方法,就是让原始模块输出一个 Promise 对象,从这个 Promise 对象判断异步操作有没有结束。<br /> <blockquote><code>// awaiting.js let output; export default (async function main() { const dynamic = await import(someMission); const data = https://www.it610.com/article/await fetch(url); output = someProcess(dynamic.default, data); })(); export { output }; </code></blockquote><br /> 上面代码中,<code>awaiting.js</code>除了输出<code>output</code>,还默认输出一个 Promise 对象(async 函数立即执行后,返回一个 Promise 对象),从这个对象判断异步操作是否结束。<br /> 下面是加载这个模块的新的写法。<br /> <blockquote><code>// usage.js import promise, { output } from "./awaiting.js"; function outputPlusValue(value) { return output + value }promise.then(() => { console.log(outputPlusValue(100)); setTimeout(() => console.log(outputPlusValue(100), 1000); }); </code></blockquote><br /> 上面代码中,将<code>awaiting.js</code>对象的输出,放在<code>promise.then()</code>里面,这样就能保证异步操作完成以后,才去读取<code>output</code>。<br /> 这种写法比较麻烦,等于要求模块的使用者遵守一个额外的使用协议,按照特殊的方法使用这个模块。一旦你忘了要用 Promise 加载,只使用正常的加载方法,依赖这个模块的代码就可能出错。而且,如果上面的<code>usage.js</code>又有对外的输出,等于这个依赖链的所有模块都要使用 Promise 加载。<br /> 顶层的<code>await</code>命令,就是为了解决这个问题。它保证只有异步操作完成,模块才会输出值。<br /> <blockquote><code>// awaiting.js const dynamic = import(someMission); const data = https://www.it610.com/article/fetch(url); export const output = someProcess((await dynamic).default, await data); </code></blockquote><br /> 上面代码中,两个异步操作在输出的时候,都加上了<code>await</code>命令。只有等到异步操作完成,这个模块才会输出值。<br /> 加载这个模块的写法如下。<br /> <blockquote><code>// usage.js import { output } from "./awaiting.js"; function outputPlusValue(value) { return output + value }console.log(outputPlusValue(100)); setTimeout(() => console.log(outputPlusValue(100), 1000); </code></blockquote><br /> 上面代码的写法,与普通的模块加载完全一样。也就是说,模块的使用者完全不用关心,依赖模块的内部有没有异步操作,正常加载即可。<br /> 这时,模块的加载会等待依赖模块(上例是<code>awaiting.js</code>)的异步操作完成,才执行后面的代码,有点像暂停在那里。所以,它总是会得到正确的<code>output</code>,不会因为加载时机的不同,而得到不一样的值。<br /> 下面是顶层<code>await</code>的一些使用场景。<br /> <blockquote><code>// import() 方法加载 const strings = await import(`/i18n/${navigator.language}`); // 数据库操作 const connection = await dbConnector(); // 依赖回滚 let jQuery; try { jQuery = await import('https://cdn-a.com/jQuery'); } catch { jQuery = await import('https://cdn-b.com/jQuery'); } </code></blockquote><br /> 注意,如果加载多个包含顶层<code>await</code>命令的模块,加载命令是同步执行的。<br /> <blockquote><code>// x.js console.log("X1"); await new Promise(r => setTimeout(r, 1000)); console.log("X2"); // y.js console.log("Y"); // z.js import "./x.js"; import "./y.js"; console.log("Z"); </code></blockquote><br /> 上面代码有三个模块,最后的<code>z.js</code>加载<code>x.js</code>和<code>y.js</code>,打印结果是<code>X1</code>、<code>Y</code>、<code>X2</code>、<code>Z</code>。这说明,<code>z.js</code>并没有等待<code>x.js</code>加载完成,再去加载<code>y.js</code>。<br /> 顶层的<code>await</code>命令有点像,交出代码的执行权给其他的模块加载,等异步操作完成后,再拿回执行权,继续向下执行。<br /> </p> <div class="dede_pages"><ul></ul></div> <div class="pcd_ad"> <center><div class="_ahwullr0ac"></div> <script type="text/javascript"> (window.slotbydup = window.slotbydup || []).push({ id: "u6834461", container: "_ahwullr0ac", async: true }); </script> <script type="text/javascript" src="//cpro.baidustatic.com/cpro/ui/cm.js" async="async" defer="defer" > </script></center> </div> <div class="mbd_ad"> <div style=margin-top:10px;margin-bottom:10px;> <div class="_i7aftr79jl"></div> <script type="text/javascript"> (window.slotbydup = window.slotbydup || []).push({ id: "u5950612", container: "_i7aftr79jl", async: true }); </script> <!-- ½űֻһ --> <script type="text/javascript" src="//cpro.baidustatic.com/cpro/ui/cm.js" async="async" defer="defer" > </script> </div> </div> <h3>推荐阅读</h3> <ul class="post-loop post-loop-default cols-0"> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/1502180.html" title="明朗的意思"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="明朗的意思" src="/images/defaultpic.gif"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/1502180.html"> <b>明朗的意思 </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/1806403.html" title="成都十大景点 这些地方你有去过吗"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="成都十大景点 这些地方你有去过吗" src="http://img.readke.com/230427/041I115Z-0-lp.jpg"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/1806403.html"> <b>成都十大景点 这些地方你有去过吗 </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/1949439.html" title="瑶柱是啥啊"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="瑶柱是啥啊" src="http://img.readke.com/230506/02431HB3-0-lp.jpg"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/1949439.html"> <b>瑶柱是啥啊 </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/0S11209242021.html" title="用波浪理论分析上证指数,近期走势豁然开朗!"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="用波浪理论分析上证指数,近期走势豁然开朗!" src="/images/defaultpic.gif"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/0S11209242021.html"> <b>用波浪理论分析上证指数,近期走势豁然开朗! </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/3628977.html" title="公众号文章发表能修改几次,公众号文章发表之后能进行几次修改"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="公众号文章发表能修改几次,公众号文章发表之后能进行几次修改" src="/images/defaultpic.gif"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/3628977.html"> <b>公众号文章发表能修改几次,公众号文章发表之后能进行几次修改 </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/993962.html" title="粉尘过敏的最好解决办法 粉尘"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="粉尘过敏的最好解决办法 粉尘" src="/images/defaultpic.gif"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/993962.html"> <b>粉尘过敏的最好解决办法 粉尘 </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/2476550.html" title="山姆烤鸡是什么鸡"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="山姆烤鸡是什么鸡" src="http://img.readke.com/230609/15445U121-0-lp.jpg"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/2476550.html"> <b>山姆烤鸡是什么鸡 </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/2626062.html" title="苹果市值有望突破两万亿美元,为什么?你怎么看?"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="苹果市值有望突破两万亿美元,为什么?你怎么看?" src="http://img.readke.com/230706/020IG021-0-lp.jpg"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/2626062.html"> <b>苹果市值有望突破两万亿美元,为什么?你怎么看? </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/2830108.html" title="新鲜莲蓬一次最多吃几个"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="新鲜莲蓬一次最多吃几个" src="/images/defaultpic.gif"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/2830108.html"> <b>新鲜莲蓬一次最多吃几个 </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/1399878.html" title="猪肝豆腐汤如何做"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="猪肝豆腐汤如何做" src="http://img.readke.com/230404/1632312K0-0-lp.jpg"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/1399878.html"> <b>猪肝豆腐汤如何做 </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/2955464.html" title="无根豆芽和有根豆芽的区别 无根豆芽和有根豆芽的区别是什么"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="无根豆芽和有根豆芽的区别 无根豆芽和有根豆芽的区别是什么" src="http://img.readke.com/230808/10502UW0-0-lp.jpg"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/2955464.html"> <b>无根豆芽和有根豆芽的区别 无根豆芽和有根豆芽的区别是什么 </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/1547508.html" title="淮北市属于哪个省 淮北属于哪一个省哪一个市"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="淮北市属于哪个省 淮北属于哪一个省哪一个市" src="http://img.readke.com/230413/2121414151-0-lp.jpg"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/1547508.html"> <b>淮北市属于哪个省 淮北属于哪一个省哪一个市 </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/3024773.html" title="火神山是什么典故"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="火神山是什么典故" src="http://img.readke.com/230818/0F22034V-0-lp.jpg"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/3024773.html"> <b>火神山是什么典故 </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/3464895.html" title="数学中的地数是什么意思 数学中的地数的解释"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="数学中的地数是什么意思 数学中的地数的解释" src="http://img.readke.com/231115/1630005325-0-lp.jpg"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/3464895.html"> <b>数学中的地数是什么意思 数学中的地数的解释 </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/4055974.html" title="mongodb not in mongodb未经"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="mongodb not in mongodb未经" src="/images/defaultpic.gif"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/4055974.html"> <b>mongodb not in mongodb未经 </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/2144334.html" title="温宁为什么叫小天使"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="温宁为什么叫小天使" src="/images/defaultpic.gif"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/2144334.html"> <b>温宁为什么叫小天使 </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/2367773.html" title="干性皮肤可以画生活妆吗"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="干性皮肤可以画生活妆吗" src="http://img.readke.com/230601/05501B4Z-0-lp.png"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/2367773.html"> <b>干性皮肤可以画生活妆吗 </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/2856089.html" title="六号台风最新消息2020 六号台风最新消息2021年"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="六号台风最新消息2020 六号台风最新消息2021年" src="http://img.readke.com/230727/0556313D7-0-lp.jpg"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/2856089.html"> <b>六号台风最新消息2020 六号台风最新消息2021年 </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/0P22EF2021.html" title="牙齿|牙齿上有小黑点,但不疼也不痒,如果长时间不管,最后会怎样?"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="牙齿|牙齿上有小黑点,但不疼也不痒,如果长时间不管,最后会怎样?" src="/images/defaultpic.gif"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/0P22EF2021.html"> <b>牙齿|牙齿上有小黑点,但不疼也不痒,如果长时间不管,最后会怎样? </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/2323095.html" title="《巫师3:狂猎》血与酒DLC刷红色突变物地点解析攻略"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="《巫师3:狂猎》血与酒DLC刷红色突变物地点解析攻略" src="http://img.readke.com/230529/221I51016-0-lp.jpg"> </a> </div> <div class="item-content"> <h4 class="item-title"> <a href="/c/2323095.html"> <b>《巫师3:狂猎》血与酒DLC刷红色突变物地点解析攻略 </b></a></h4> <div class="item-meta"> <div class="item-meta-right"> </div> </div> </div> </li> </ul> <p><br /><ul class="post-loop post-loop-list cols-4"><li><a href="/c/021T5HF2022.html" title="一起来学习C语言的字符串转换函数" target="_blank">一起来学习C语言的字符串转换函数 </a></li> <li><a href="/c/021T5HE2022.html" title="C语言字符函数中的isalnum()和iscntrl()你都知道吗" target="_blank">C语言字符函数中的isalnum()和iscntrl()你都知道吗 </a></li> <li><a href="/c/021T5HA2022.html" title="C语言浮点函数中的modf和fmod详解" target="_blank">C语言浮点函数中的modf和fmod详解 </a></li> <li><a href="/c/021T5H622022.html" title="C语言中的时间函数clock()和time()你都了解吗" target="_blank">C语言中的时间函数clock()和time()你都了解吗 </a></li> <li><a href="/c/021T5D942022.html" title="感恩日记第111篇2020.02.06" target="_blank">感恩日记第111篇2020.02.06 </a></li> <li><a href="/c/021T5B592022.html" title="概率论/统计学|随机变量 的 分布函数 与 概率密度函数 的区别" target="_blank">概率论/统计学|随机变量 的 分布函数 与 概率密度函数 的区别 </a></li> <li><a href="/c/021J553962022.html" title="vue组件中为何data必须是一个函数()" target="_blank">vue组件中为何data必须是一个函数() </a></li> <li><a href="/c/021J550L2022.html" title="iOS-Swift-map|iOS-Swift-map filter reduce、函数式编程" target="_blank">iOS-Swift-map|iOS-Swift-map filter reduce、函数式编程 </a></li> <li><a href="/c/021J550612022.html" title="2020.02.12/三|2020.02.12/三 农历正月十九日 日检视" target="_blank">2020.02.12/三|2020.02.12/三 农历正月十九日 日检视 </a></li> <li><a href="/c/021J550312022.html" title="CocoaAsyncSocket|CocoaAsyncSocket (GCDAsyncSocket)适配IPv6" target="_blank">CocoaAsyncSocket|CocoaAsyncSocket (GCDAsyncSocket)适配IPv6 </a></li> </ul></p> <div class=entry-copyright> <p></p> </div> </div> <div class="entry-footer"> <div class="prev-next sb br mb clearfix"> <p class="post-prev fl ellipsis">上一篇:<a href='/c/0Z21334312021.html'>午后读书</a> </p> <p class="post-next fr ellipsis">下一篇:<a href='/c/0Z21334332021.html'>【读书营成长记录】|【读书营成长记录】 改变不是说说而已</a> </p> </div> </div> </div> </article> </main> <aside class="sidebar"> <div class="widget widget_post_thumb"> <h3 class="widget-title"><span>更多...</span></h3> <ul> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/021J553332022.html" title="mysql中视图事务索引与权限管理"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="mysql中视图事务索引与权限管理" src="/images/defaultpic.gif"> </a></div> <div class="item-content"> <p class="item-title"><a href="/c/021J553332022.html" title="mysql中视图事务索引与权限管理">mysql中视图事务索引与权限管理</a></p> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/0Z11235322021.html" title="蚂蚁金服|蚂蚁金服 Service Mesh 渐进式迁移方案|Service Mesh Meetup 实录"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="蚂蚁金服|蚂蚁金服 Service Mesh 渐进式迁移方案|Service Mesh Meetup 实录" src="/images/defaultpic.gif"> </a></div> <div class="item-content"> <p class="item-title"><a href="/c/0Z11235322021.html" title="蚂蚁金服|蚂蚁金服 Service Mesh 渐进式迁移方案|Service Mesh Meetup 实录">蚂蚁金服|蚂蚁金服 Service Mesh 渐进式迁移方案|Service Mesh Meetup 实录</a></p> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/09121c32R021.html" title="离开职场,如何自救"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="离开职场,如何自救" src="/images/defaultpic.gif"> </a></div> <div class="item-content"> <p class="item-title"><a href="/c/09121c32R021.html" title="离开职场,如何自救">离开职场,如何自救</a></p> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/12123595502021.html" title="2018-09-28|2018-09-28 永远的徐渭"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="2018-09-28|2018-09-28 永远的徐渭" src="/images/defaultpic.gif"> </a></div> <div class="item-content"> <p class="item-title"><a href="/c/12123595502021.html" title="2018-09-28|2018-09-28 永远的徐渭">2018-09-28|2018-09-28 永远的徐渭</a></p> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/0925245EH021.html" title="黑天鹅11"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="黑天鹅11" src="/images/defaultpic.gif"> </a></div> <div class="item-content"> <p class="item-title"><a href="/c/0925245EH021.html" title="黑天鹅11">黑天鹅11</a></p> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/11213353642021.html" title="四十一、Qt之 QSqlTableModel 实现分页"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="四十一、Qt之 QSqlTableModel 实现分页" src="/images/defaultpic.gif"> </a></div> <div class="item-content"> <p class="item-title"><a href="/c/11213353642021.html" title="四十一、Qt之 QSqlTableModel 实现分页">四十一、Qt之 QSqlTableModel 实现分页</a></p> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/091R220a2021.html" title="公益与慈善"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="公益与慈善" src="/images/defaultpic.gif"> </a></div> <div class="item-content"> <p class="item-title"><a href="/c/091R220a2021.html" title="公益与慈善">公益与慈善</a></p> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/0Z5154U92021.html" title="秦始皇为什么造兵马俑"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="秦始皇为什么造兵马俑" src="/images/defaultpic.gif"> </a></div> <div class="item-content"> <p class="item-title"><a href="/c/0Z5154U92021.html" title="秦始皇为什么造兵马俑">秦始皇为什么造兵马俑</a></p> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/020643R242022.html" title="不是我不懂温柔,而是你并非我爱的人"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="不是我不懂温柔,而是你并非我爱的人" src="/images/defaultpic.gif"> </a></div> <div class="item-content"> <p class="item-title"><a href="/c/020643R242022.html" title="不是我不懂温柔,而是你并非我爱的人">不是我不懂温柔,而是你并非我爱的人</a></p> </div> </li> <li class="item"> <div class="item-img"> <a class="item-img-inner" href="/c/121135B3H021.html" title="Intellij|Intellij IDEA 快捷键"> <img width="480" height="300" class="attachment-default size-default wp-post-image j-lazy" alt="Intellij|Intellij IDEA 快捷键" src="/images/defaultpic.gif"> </a></div> <div class="item-content"> <p class="item-title"><a href="/c/121135B3H021.html" title="Intellij|Intellij IDEA 快捷键">Intellij|Intellij IDEA 快捷键</a></p> </div> </li> </ul> </div> </aside> </div> </div> <footer class="footer"> <div class="container"> <div class="clearfix"> <div class="footer-col footer-col-logo"> <!--<img src="/skin/images/logo-footer.png">--></div> <div class="footer-col footer-col-copy"> <ul class="footer-nav hidden-xs"> <li class="menu-item menu-item-706"><a href="/baike/">生活百科</a></li> <li class="menu-item menu-item-706"><a href="/it/">it技术</a></li> </ul> <div class="copyright"> <p>Copyright © 2017-2022 锐客网 <a href="http://beian.miit.gov.cn/" target="_blank" rel="nofollow">京ICP备11041112号-41</a> </p> </div> </div> <div class="footer-col footer-col-sns"> <div class="footer-sns"> </div> </div> </div> </div> </footer> <div class="action action-style-0 action-color-0 action-pos-1" style="bottom:15%;"> <div class="action-item gotop j-top"> <i class="web-icon wi action-item-icon"><svg aria-hidden="true"> <use xlink:href="#wi-arrow-up-2"></use> </svg></i></div> </div> <script> var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?79e4e485d34c6fc717489eaa10b314e3"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); </script> </div> <script>var _web_js={};</script> <script src="/skin/js/index.js"></script> </body> </html>