JavaScript异步编程解决方案

Stella981
• 阅读 946

Generator 函数

学习指导:阮一峰ES6 -- Generator函数

基本概念

  • 语法上:Generator 函数是一个状态机,封装了多个内部状态;执行 Generator 函数会返回一个遍历器对象。也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

  • 形式上:Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号*;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

    function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator();

    // 定义了一个 Generator 函数helloWorldGenerator,它内部有两个yield表达式(hello和world) // 即该函数有三个状态:hello,world 和 return 语句(结束执行)。

  • 调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。

    // 上述函数下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。 // 即每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行, // 直到遇到下一个yield表达式(或return语句)为止。 // 换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

    hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } hw.next() // { value: undefined, done: true }

    /* 解释说明: 上面代码一共调用了四次next方法。 第一次调用,Generator 函数开始执行,直到遇到第一个yield表达式为止。 next方法返回一个对象,它的value属性就是当前yield表达式的值hello, done属性的值false,表示遍历还没有结束。

    第二次调用,Generator 函数从上次yield表达式停下的地方,一直执行到下一个yield表达式。 next方法返回的对象的value属性就是当前yield表达式的值world, done属性的值false,表示遍历还没有结束。

    第三次调用,Generator 函数从上次yield表达式停下的地方,一直执行到return语句 (如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性, 就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined), done属性的值true,表示遍历已经结束。

    第四次调用,此时 Generator 函数已经运行完毕, next方法返回对象的value属性为undefined,done属性为true。 以后再调用next方法,返回的都是这个值。 */

yield 表达式说明

  • Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

  • 遍历器对象的next方法的运行逻辑如下。

    • 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
    • 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
    • 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
    • 如果该函数没有return语句,则返回的对象的 value 属性值为 undefined。
  • 需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。

    function* gen(){ yield 123 + 456; }

    // yield后面的表达式123 + 456,不会立即求值,只会在next方法将指针移到这一句时,才会求值.

yield 表达式语法

  • yield表达式只能用在 Generator 函数里面,用在其他地方都会报错

    (function (){ yield 1; })() // SyntaxError: Unexpected number

  • yield表达式如果用在另一个表达式之中,必须放在圆括号里面

    function* demo() { console.log('Hello' + yield); // SyntaxError console.log('Hello' + yield 123); // SyntaxError

    console.log('Hello' + (yield)); // OK console.log('Hello' + (yield 123)); // OK }

  • yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号

    function* demo() { foo(yield 'a', yield 'b'); // OK let input = yield; // OK }

用法实例讲解

var arr = [1, [[2, 3], 4], [5, 6]];

var flat = function* (a) {
  a.forEach(function (item) {
    if (typeof item !== 'number') {
      yield* flat(item);
    } else {
      yield item;
    }
  });
};  // 错误用法
// forEach方法的参数是一个普通函数,但是在里面使用了yield表达式

// 一种修改方法是改用for循环。
var flat = function* (a) {
  var length = a.length;
  for (var i = 0; i < length; i++) {
    var item = a[i];
    if (typeof item !== 'number') {
      yield* flat(item);
    } else {
      yield item;
    }
  }
};

for (var f of flat(arr)) {
  console.log(f);
}
// 1, 2, 3, 4, 5, 6

yield表达式与return语句

  • yield 表达式与return 语句既有相似之处,也有区别。
  • 相似之处在于,都能返回紧跟在语句后面的那个表达式的值。
  • 区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。
  • 一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield表达式
  • 正常函数只能返回一个值,因为只能执行一次return;
  • Generator 函数可以返回一系列的值,因为可以有任意多个yield
  • 从另一个角度看,也可以说 Generator 生成了一系列的值。

next 方法的参数

  • yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

    function* f() { for(var i = 0; true; i++) { var reset = yield i; if(reset) { i = -1; } } } var g = f();

    g.next() // { value: 0, done: false } g.next() // { value: 1, done: false } g.next(true) // { value: 0, done: false }

    /* 上面代码先定义了一个可以无限运行的 Generator 函数f, 如果next方法没有参数,每次运行到yield表达式,变量reset的值总是undefined。

    当next方法带一个参数true时,变量reset就被重置为这个参数(即true),因此i会等于-1, 下一轮循环就会从-1开始递增。 */

  • 这个功能有很重要的语法意义。Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。

  • 通过next方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值。也就是说,可以在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。

    function* foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); }

    var a = foo(5); a.next() // Object{value:6, done:false} a.next() // Object{value:NaN, done:false} a.next() // Object{value:NaN, done:true}

    var b = foo(5); b.next() // { value:6, done:false } b.next(12) // { value:8, done:false } b.next(13) // { value:42, done:true }

    /* 上面代码中,第二次运行next方法的时候不带参数,导致 y 的值等于2 * undefined(即NaN), 除以 3 以后还是NaN,因此返回对象的value属性也等于NaN。 第三次运行Next方法的时候不带参数,所以z等于undefined, 返回对象的value属性等于5 + NaN + undefined,即NaN。

    如果向next方法提供参数,返回结果就完全不一样了。 上面代码第一次调用b的next方法时,返回x+1的值6; 第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y / 3的值8; 第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13, 这时x等于5,y等于24,所以return语句的值等于42。 */

  • 注意,由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

for...of 循环

    1. for...of循环可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法。

    function* foo() { yield 1; yield 2; yield 3; yield 4; yield 5; return 6; } for (let v of foo()) { console.log(v); } // 1 2 3 4 5

    // 上面代码使用for...of循环,依次显示 5 个yield表达式的值。 // 这里需要注意,一旦next方法的返回对象的done属性为true, // for...of循环就会中止,且不包含该返回对象, // 所以上面代码的return语句返回的6,不包括在for...of循环之中。

  • 2)利用for...of循环,可以写出遍历任意对象(object)的方法。原生的 JavaScript 对象没有遍历接口,无法使用for...of循环,通过 Generator 函数为它加上这个接口,就可以用了。

    function* objectEntries(obj) { let propKeys = Reflect.ownKeys(obj);

    for (let propKey of propKeys) { yield [propKey, obj[propKey]]; } }

    let jane = { first: 'Jane', last: 'Doe' };

    for (let [key, value] of objectEntries(jane)) { console.log(${key}: ${value}); } // first: Jane // last: Doe

  • 3)加上遍历器接口的另一种写法是,将 Generator 函数加到对象的Symbol.iterator属性上面

    function* objectEntries() { let propKeys = Object.keys(this);

    for (let propKey of propKeys) { yield [propKey, this[propKey]]; } }

    let jane = { first: 'Jane', last: 'Doe' };

    jane[Symbol.iterator] = objectEntries;

    for (let [key, value] of jane) { console.log(${key}: ${value}); } // first: Jane // last: Doe

  • 4)除了for...of循环以外,扩展运算符(...)解构赋值Array.from方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。

    function* numbers () { yield 1 yield 2 return 3 yield 4 }

    // 扩展运算符 [...numbers()] // [1, 2]

    // Array.from 方法 Array.from(numbers()) // [1, 2]

    // 解构赋值 let [x, y] = numbers(); x // 1 y // 2

    // for...of 循环 for (let n of numbers()) { console.log(n) } // 1 // 2

Generator函数的异步操作

function* asyncJob() {
  // ...其他代码
  var f = yield readFile(fileA);
  // ...其他代码
}

函数asyncJob是一个协程,它的奥妙就在其中的yield命令。它表示执行到此处,执行权将交给其他协程。 也就是说,yield命令是异步两个阶段的分界线。 协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作,如果去除yield命令,简直一模一样。

Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。
整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用yield语句注明。

异步任务的封装

var fetch = require('node-fetch');

function* gen(){
  var url = 'https://api.github.com/users/github';
  var result = yield fetch(url);
  console.log(result.bio);
}

// Generator 函数封装了一个异步操作,该操作先读取一个远程接口,然后从 JSON 格式的数据解析信息。
// 就像前面说过的,这段代码非常像同步操作,除了加上了yield命令。

// 执行上述代码
var g = gen();
var result = g.next();

result.value.then(function(data){
  return data.json();
}).then(function(data){
  g.next(data);
});

// 首先执行 Generator 函数,获取遍历器对象,然后使用next方法(第二行),执行异步任务的第一阶段。
// 由于Fetch模块返回的是一个 Promise 对象,因此要用then方法调用下一个next方法。

虽然 Generator 函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。

async 函数

学习指导:阮一峰ES6 -- 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()); };

    // 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 函数的改进,体现在以下四点:

    • 内置执行器 —— Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
    • 更好的语义 —— async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
    • 更广的适用性 —— co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
    • 返回值是 Promise —— async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

基本用法

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

  • 引入demo

    /* async 表示异步调用 返回一个Promise对象 / async function timeout(ms) { await new Promise((resolve) => { setTimeout(resolve, ms); }); } / 与下边等价*/

    // function timeout(ms) { // return new Promise((resolve) => { // setTimeout(resolve, ms); // }); // }

    async function asyncPrint(value, ms) { await timeout(ms); console.log(value); }

    asyncPrint('hello world', 3000) // 3000毫秒以后,输出hello world

  • 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('<a href="https://www.helloworld.net/redirect?target=https://tc39.github.io/ecma262/').then(console.log)" rel="nofollow noopener noreferrer" target="_blank">https://tc39.github.io/ecma262/').then(console.log)</a> // "ECMAScript 2017 Language Specification"</p> <p>// 函数getTitle内部有三个操作:抓取网页、取出文本、匹配页面标题。 // 只有这三个操作全部完成,才会执行then方法里面的 console.log。</p> </li> </ul> </li> <li><p>await 命令</p> <ul> <li><p>正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。</p> <p>async function f() { return await 123; }</p> <p>f().then(v => console.log(v)) // 123</p> <p>// await命令的参数是数值123,它被转成 Promise 对象,并立即resolve</p> </li> <li><p>await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。</p> <p>async function f() { await Promise.reject('出错了'); }</p> <p>f() .then(v => console.log(v)) .catch(e => console.log(e)) // 出错了</p> <p>// await 语句前面没有 return,但是 reject 方法的参数依然传入了 catch 方法的回调函数。 // 这里如果在await前面加上return,效果是一样的。</p> </li> <li><p>只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行</p> <p>async function f() { await Promise.reject('出错了'); await Promise.resolve('hello world'); // 不会执行 }</p> </li> <li><p>可以将第一个await放在<code>try...catch</code>结构里面,这样不管这个异步操作是否成功,第二个await都会执行。</p> <p>async function f() { try { await Promise.reject('出错了'); } catch(e) { } return await Promise.resolve('hello world'); }</p> <p>f() .then(v => console.log(v)) // hello world</p> </li> <li><p>另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误</p> <p>async function f() { await Promise.reject('出错了') .catch(e => console.log(e)); return await Promise.resolve('hello world'); }</p> <p>f() .then(v => console.log(v)) // 出错了 // hello world</p> </li> </ul> </li> </ul> <h3 id="使用注意点">使用注意点</h3> <ul> <li><p>第一点,前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在<code>try...catch</code>代码块中。</p> </li> <li><p>第二点,多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。</p> <p>let foo = await getFoo(); let bar = await getBar(); // 上面代码中,getFoo和getBar是两个独立的异步操作(即互不依赖),被写成继发关系。 // 这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。</p> <p>// 写法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 写法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise; // getFoo和getBar都是同时触发,这样就会缩短程序的执行时间</p> </li> <li><p>第三点,await命令只能用在async函数之中,如果用在普通函数,就会报错。</p> <p>async function dbFuc(db) { let docs = [{}, {}, {}];</p> <p> // 报错 docs.forEach(function (doc) { await db.post(doc); }); } // 上面代码会报错,因为await用在普通函数之中了</p> <p>// 采用for循环 async function dbFuc(db) { let docs = [{}, {}, {}];</p> <p> for (let doc of docs) { await db.post(doc); } }</p> <p>// 如果确实希望多个请求并发执行,可以使用Promise.all方法。 // 当三个请求都会resolved时,下面两种写法效果相同。 async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc));</p> <p> let results = await Promise.all(promises); console.log(results); }</p> <p>// 或者使用下面的写法 async function dbFuc(db) { let docs = [{}, {}, {}]; let promises = docs.map((doc) => db.post(doc));</p> <p> let results = []; for (let promise of promises) { results.push(await promise); } console.log(results); }</p> </li> </ul> <h3 id="async函数实现原理">async函数实现原理</h3> <ul> <li><p>async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里</p> <p>async function fn(args) { // ... }</p> <p>// 等同于 function fn(args) { return spawn(function* () { // ... }); }</p> <p>// 所有的async函数都可以写成上面的第二种形式,其中的spawn函数就是自动执行器。</p> </li> </ul> <h3 id="场景再现">场景再现</h3> <ul> <li><p>假定某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值。</p> <p>async function chainAnimationsAsync(elem, animations) { let ret = null; // 变量ret用来保存上一个动画的返回值 try { for(let anim of animations) { ret = await anim(elem); } } catch(e) { /* 忽略错误,继续执行 */ } return ret; }</p> </li> <li><p>依次远程读取一组 URL,然后按照读取的顺序输出结果。</p> <p>// 远程操作继发.只有前一个 URL 返回结果,才会去读取下一个 URL,这样做效率很差,非常浪费时间 async function logInOrder(urls) { for (const url of urls) { const response = await fetch(url); console.log(await response.text()); } }</p> <p>// 并发发出远程请求 async function logInOrder(urls) { // 并发读取远程URL const textPromises = urls.map(async url => { const response = await fetch(url); return response.text(); });</p> <p> // 按次序输出 for (const textPromise of textPromises) { console.log(await textPromise); } }</p> </li> </ul> </div> </article> <div class="tags-box" data-v-1bdff09d><a href="/tag/async.html" target="_blank" class="item" data-v-1bdff09d>async</a><a href="/tag/promise.html" target="_blank" class="item" data-v-1bdff09d>promise</a><a href="/tag/%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8.html" target="_blank" class="item" data-v-1bdff09d>函数调用</a><a href="/tag/next.html" target="_blank" class="item" data-v-1bdff09d>next</a><a href="/tag/const.html" target="_blank" class="item" data-v-1bdff09d>const</a><a href="/tag/yield.html" target="_blank" class="item" data-v-1bdff09d>yield</a><a href="/tag/%E5%89%8D%E7%AB%AF.html" target="_blank" class="item" data-v-1bdff09d>前端</a><a href="/tag/%E5%89%8D%E7%AB%AF.html" target="_blank" class="item" data-v-1bdff09d>前端</a></div> <div class="blog-btns-box" data-v-7d5edd21><div class="btn-item" data-v-7d5edd21><div class="circle zan" data-v-7d5edd21><IconFont type="icon-Dianzan" class="iconfont" data-v-7d5edd21></IconFont></div> <div class="text" data-v-7d5edd21>点赞</div></div> <div class="btn-item" data-v-7d5edd21><div class="circle favorite" data-v-7d5edd21><IconFont type="icon-Like1" class="iconfont" data-v-7d5edd21></IconFont></div> <div class="text" data-v-7d5edd21>收藏</div></div></div></div> <div id="recommend-lesson"><div class="recommend-lesson-title">推荐课程</div> <div class="horizontal" data-v-ba84c88a><a target="_blank" href="/lesson/detail/7011629094" class="re-course-list" data-v-ba84c88a><img src="https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/80fd22993d90e1283227f808922efd96.png" alt="avatar" data-v-ba84c88a> <div class="re-info" data-v-ba84c88a><h2 data-v-ba84c88a>Andriod设计模式实战</h2> <div class="des" data-v-ba84c88a><div class="price" data-v-ba84c88a>免费</div> <span data-v-ba84c88a>37人学习</span></div></div></a><a target="_blank" href="/lesson/detail/4889453415" class="re-course-list" data-v-ba84c88a><img src="https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/c4b7688afeb6ec0a56c562034d1ed6e2.png" alt="avatar" data-v-ba84c88a> <div class="re-info" data-v-ba84c88a><h2 data-v-ba84c88a>Android进阶之音视频技术</h2> <div class="des" data-v-ba84c88a><div class="price" data-v-ba84c88a>免费</div> <span data-v-ba84c88a>19人学习</span></div></div></a></div></div> <div id="anchor" class="comment-container"><div id="comment-panel" class="comment-panel" data-v-25426b80><div class="panel-title" data-v-25426b80>评论区</div> <div class="comment-input-box" data-v-25426b80><img src="/_nuxt/img/default-avatar.38df358.png" alt class="user-avatar" data-v-25426b80> <!----></div> <!----></div></div> <div class="recommend-blog-list"><div class="recommend-title">推荐文章</div> <div class="art-list" data-v-6293c55f><div class="blog-item" data-v-6293c55f><div class="blog-header" data-v-6293c55f><img src="https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/cd34cdaab69ede1e2bf28de235151c34.webp" alt="blmius" class="img" data-v-6293c55f> <a href="/blmius" target="_blank" class="name" data-v-6293c55f> blmius </a> <div class="dot" data-v-6293c55f>•</div> <div class="time" data-v-6293c55f> 3年前 </div></div> <div class="blog-content" data-v-6293c55f><div class="item-left" data-v-6293c55f><a href="/p/2802547579" target="_blank" title="MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1" class="title single-ellipsis" data-v-6293c55f> MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1 </a> <div class="intro multi-ellipsis-2" data-v-6293c55f> 文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s </div></div> <img src="https://img-hello-world.oss-cn-beijing.aliyuncs.com/ed6c6174f1775f65a3fbe98488391419.png" class="item-right" data-v-6293c55f></div> <!----> <div class="blog-footer flex" data-v-6293c55f><div class="iconfont footer-item icon-Show1" data-v-6293c55f><span class="num" data-v-6293c55f>4252</span></div><div class="iconfont footer-item icon-Guanbi1-copy" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div><div class="iconfont footer-item icon-Pinglun1" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div></div></div><div class="blog-item" data-v-6293c55f><div class="blog-header" data-v-6293c55f><img src="https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/3149e13a96d0d27780fd76b05b6ea101.jfif" alt="Easter79" class="img" data-v-6293c55f> <a href="/Easter79" target="_blank" class="name" data-v-6293c55f> Easter79 </a> <div class="dot" data-v-6293c55f>•</div> <div class="time" data-v-6293c55f> 3年前 </div></div> <div class="blog-content" data-v-6293c55f><div class="item-left" data-v-6293c55f><a href="/p/0507154872" target="_blank" title="swap空间的增减方法 " class="title single-ellipsis" data-v-6293c55f> swap空间的增减方法 </a> <div class="intro multi-ellipsis-2" data-v-6293c55f> (1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap </div></div> <!----></div> <!----> <div class="blog-footer flex" data-v-6293c55f><div class="iconfont footer-item icon-Show1" data-v-6293c55f><span class="num" data-v-6293c55f>1304</span></div><div class="iconfont footer-item icon-Guanbi1-copy" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div><div class="iconfont footer-item icon-Pinglun1" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div></div></div><div class="blog-item" data-v-6293c55f><div class="blog-header" data-v-6293c55f><img src="https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/0d250ec5492da88592013bbd302402ae.jfif" alt="helloworld_34035044" class="img" data-v-6293c55f> <a href="/34035044" target="_blank" class="name" data-v-6293c55f> helloworld_34035044 </a> <div class="dot" data-v-6293c55f>•</div> <div class="time" data-v-6293c55f> 2年前 </div></div> <div class="blog-content" data-v-6293c55f><div class="item-left" data-v-6293c55f><a href="/p/9382788506" target="_blank" title="皕杰报表之UUID" class="title single-ellipsis" data-v-6293c55f> 皕杰报表之UUID </a> <div class="intro multi-ellipsis-2" data-v-6293c55f> ​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为 </div></div> <!----></div> <!----> <div class="blog-footer flex" data-v-6293c55f><div class="iconfont footer-item icon-Show1" data-v-6293c55f><span class="num" data-v-6293c55f>1214</span></div><div class="iconfont footer-item icon-Guanbi1-copy" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div><div class="iconfont footer-item icon-Pinglun1" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div></div></div><div class="blog-item" data-v-6293c55f><div class="blog-header" data-v-6293c55f><img src="https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/4f77da7232573922a580077ad6e6e085.jpg" alt="待兔" class="img" data-v-6293c55f> <a href="/waitrabbit" target="_blank" class="name" data-v-6293c55f> 待兔 </a> <div class="dot" data-v-6293c55f>•</div> <div class="time" data-v-6293c55f> 5个月前 </div></div> <div class="blog-content" data-v-6293c55f><div class="item-left" data-v-6293c55f><a href="/p/1863625810" target="_blank" title="手写Java HashMap源码" class="title single-ellipsis" data-v-6293c55f> 手写Java HashMap源码 </a> <div class="intro multi-ellipsis-2" data-v-6293c55f> HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22 </div></div> <!----></div> <!----> <div class="blog-footer flex" data-v-6293c55f><div class="iconfont footer-item icon-Show1" data-v-6293c55f><span class="num" data-v-6293c55f>1137</span></div><div class="iconfont footer-item icon-Guanbi1-copy" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div><div class="iconfont footer-item icon-Pinglun1" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div></div></div><div class="blog-item" data-v-6293c55f><div class="blog-header" data-v-6293c55f><img src="https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/d4566ad9edfb956dba92fc21f5e1561f.webp" alt="Jacquelyn38" class="img" data-v-6293c55f> <a href="/Jacquelyn38" target="_blank" class="name" data-v-6293c55f> Jacquelyn38 </a> <div class="dot" data-v-6293c55f>•</div> <div class="time" data-v-6293c55f> 3年前 </div></div> <div class="blog-content" data-v-6293c55f><div class="item-left" data-v-6293c55f><a href="/p/3468005401" target="_blank" title="2020年前端实用代码段,为你的工作保驾护航" class="title single-ellipsis" data-v-6293c55f> 2020年前端实用代码段,为你的工作保驾护航 </a> <div class="intro multi-ellipsis-2" data-v-6293c55f> 有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number ) </div></div> <img src="https://img-hello-world.oss-cn-beijing.aliyuncs.com/e7130dfd1edf85ccee4a9e73aa0b77dd.jpeg" class="item-right" data-v-6293c55f></div> <!----> <div class="blog-footer flex" data-v-6293c55f><div class="iconfont footer-item icon-Show1" data-v-6293c55f><span class="num" data-v-6293c55f>2448</span></div><div class="iconfont footer-item icon-Guanbi1-copy" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div><div class="iconfont footer-item icon-Pinglun1" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div></div></div><div class="blog-item" data-v-6293c55f><div class="blog-header" data-v-6293c55f><img src="https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/46847d754406b0102dee7a1f54d14f92.jfif" alt="Wesley13" class="img" data-v-6293c55f> <a href="/Wesley13" target="_blank" class="name" data-v-6293c55f> Wesley13 </a> <div class="dot" data-v-6293c55f>•</div> <div class="time" data-v-6293c55f> 3年前 </div></div> <div class="blog-content" data-v-6293c55f><div class="item-left" data-v-6293c55f><a href="/p/6029686498" target="_blank" title="mysql设置时区 " class="title single-ellipsis" data-v-6293c55f> mysql设置时区 </a> <div class="intro multi-ellipsis-2" data-v-6293c55f> mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0 </div></div> <!----></div> <!----> <div class="blog-footer flex" data-v-6293c55f><div class="iconfont footer-item icon-Show1" data-v-6293c55f><span class="num" data-v-6293c55f>1382</span></div><div class="iconfont footer-item icon-Guanbi1-copy" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div><div class="iconfont footer-item icon-Pinglun1" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div></div></div><div class="blog-item" data-v-6293c55f><div class="blog-header" data-v-6293c55f><img src="https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/1bad3e5246214111b0d7a482fc5beec5.jfif" alt="Stella981" class="img" data-v-6293c55f> <a href="/Stella981" target="_blank" class="name" data-v-6293c55f> Stella981 </a> <div class="dot" data-v-6293c55f>•</div> <div class="time" data-v-6293c55f> 3年前 </div></div> <div class="blog-content" data-v-6293c55f><div class="item-left" data-v-6293c55f><a href="/p/1930020008" target="_blank" title="HIVE 时间操作函数 " class="title single-ellipsis" data-v-6293c55f> HIVE 时间操作函数 </a> <div class="intro multi-ellipsis-2" data-v-6293c55f> 日期函数UNIX时间戳转日期函数: from\_unixtime语法:   from\_unixtime(bigint unixtime\, string format\)返回值: string说明: 转化UNIX时间戳(从19700101 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式举例:hive   selec </div></div> <!----></div> <!----> <div class="blog-footer flex" data-v-6293c55f><div class="iconfont footer-item icon-Show1" data-v-6293c55f><span class="num" data-v-6293c55f>962</span></div><div class="iconfont footer-item icon-Guanbi1-copy" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div><div class="iconfont footer-item icon-Pinglun1" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div></div></div><div class="blog-item" data-v-6293c55f><div class="blog-header" data-v-6293c55f><img src="https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/46847d754406b0102dee7a1f54d14f92.jfif" alt="Wesley13" class="img" data-v-6293c55f> <a href="/Wesley13" target="_blank" class="name" data-v-6293c55f> Wesley13 </a> <div class="dot" data-v-6293c55f>•</div> <div class="time" data-v-6293c55f> 3年前 </div></div> <div class="blog-content" data-v-6293c55f><div class="item-left" data-v-6293c55f><a href="/p/4384340365" target="_blank" title="00:Java简单了解 " class="title single-ellipsis" data-v-6293c55f> 00:Java简单了解 </a> <div class="intro multi-ellipsis-2" data-v-6293c55f> 浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。 </div></div> <!----></div> <!----> <div class="blog-footer flex" data-v-6293c55f><div class="iconfont footer-item icon-Show1" data-v-6293c55f><span class="num" data-v-6293c55f>1316</span></div><div class="iconfont footer-item icon-Guanbi1-copy" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div><div class="iconfont footer-item icon-Pinglun1" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div></div></div><div class="blog-item" data-v-6293c55f><div class="blog-header" data-v-6293c55f><img src="https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/1bad3e5246214111b0d7a482fc5beec5.jfif" alt="Stella981" class="img" data-v-6293c55f> <a href="/Stella981" target="_blank" class="name" data-v-6293c55f> Stella981 </a> <div class="dot" data-v-6293c55f>•</div> <div class="time" data-v-6293c55f> 3年前 </div></div> <div class="blog-content" data-v-6293c55f><div class="item-left" data-v-6293c55f><a href="/p/3395108718" target="_blank" title="Django中Admin中的一些参数配置 " class="title single-ellipsis" data-v-6293c55f> Django中Admin中的一些参数配置 </a> <div class="intro multi-ellipsis-2" data-v-6293c55f> 设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable </div></div> <!----></div> <!----> <div class="blog-footer flex" data-v-6293c55f><div class="iconfont footer-item icon-Show1" data-v-6293c55f><span class="num" data-v-6293c55f>1237</span></div><div class="iconfont footer-item icon-Guanbi1-copy" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div><div class="iconfont footer-item icon-Pinglun1" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div></div></div><div class="blog-item" data-v-6293c55f><div class="blog-header" data-v-6293c55f><img src="https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/46847d754406b0102dee7a1f54d14f92.jfif" alt="Wesley13" class="img" data-v-6293c55f> <a href="/Wesley13" target="_blank" class="name" data-v-6293c55f> Wesley13 </a> <div class="dot" data-v-6293c55f>•</div> <div class="time" data-v-6293c55f> 3年前 </div></div> <div class="blog-content" data-v-6293c55f><div class="item-left" data-v-6293c55f><a href="/p/2102334911" target="_blank" title="MySQL部分从库上面因为大量的临时表tmp_table造成慢查询 " class="title single-ellipsis" data-v-6293c55f> MySQL部分从库上面因为大量的临时表tmp_table造成慢查询 </a> <div class="intro multi-ellipsis-2" data-v-6293c55f> 背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_ </div></div> <!----></div> <!----> <div class="blog-footer flex" data-v-6293c55f><div class="iconfont footer-item icon-Show1" data-v-6293c55f><span class="num" data-v-6293c55f>1588</span></div><div class="iconfont footer-item icon-Guanbi1-copy" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div><div class="iconfont footer-item icon-Pinglun1" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div></div></div><div class="blog-item" data-v-6293c55f><div class="blog-header" data-v-6293c55f><img src="https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/ff45645a048833b06e967323e7411c91.webp" alt="Python进阶者" class="img" data-v-6293c55f> <a href="/pdcfighting" target="_blank" class="name" data-v-6293c55f> Python进阶者 </a> <div class="dot" data-v-6293c55f>•</div> <div class="time" data-v-6293c55f> 11个月前 </div></div> <div class="blog-content" data-v-6293c55f><div class="item-left" data-v-6293c55f><a href="/p/7920817787" target="_blank" title="Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除" class="title single-ellipsis" data-v-6293c55f> Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除 </a> <div class="intro multi-ellipsis-2" data-v-6293c55f> 大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这 </div></div> <img src="https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/aeaded5a375b9d90ad54a2731b017114.png" class="item-right" data-v-6293c55f></div> <!----> <div class="blog-footer flex" data-v-6293c55f><div class="iconfont footer-item icon-Show1" data-v-6293c55f><span class="num" data-v-6293c55f>559</span></div><div class="iconfont footer-item icon-Guanbi1-copy" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div><div class="iconfont footer-item icon-Pinglun1" data-v-6293c55f><span class="num" data-v-6293c55f>0</span></div></div></div> </div></div></div> <div class="blog-right-container"><div class="personal-info" data-v-af95cca0><div class="base-info" data-v-af95cca0><img src="https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/1bad3e5246214111b0d7a482fc5beec5.jfif" alt="Stella981" class="avatar" data-v-af95cca0> <div class="info-content" data-v-af95cca0><div onc class="name-level" data-v-af95cca0><div class="name single-ellipsis" data-v-af95cca0> Stella981 </div> <div class="level-icon level-1" data-v-429e45df data-v-af95cca0> Lv1 </div></div> <!----></div></div> <button type="button" class="follow-btn ant-btn ant-btn-primary ant-btn-background-ghost" data-v-af95cca0><span>关 注</span></button> <div class="signature" data-v-af95cca0>接天莲叶无穷碧,映日荷花别样红。</div> <div class="article-fans-stars" data-v-af95cca0><div class="item" data-v-af95cca0><div class="label" data-v-af95cca0>文章</div> <div class="value" data-v-af95cca0> 1.7w </div></div><div class="item" data-v-af95cca0><div class="label" data-v-af95cca0>粉丝</div> <div class="value" data-v-af95cca0> 42 </div></div><div class="item" data-v-af95cca0><div class="label" data-v-af95cca0>获赞</div> <div class="value" data-v-af95cca0> 28 </div></div></div> <div class="author-social-info" data-v-af95cca0><!----> <!----> <!----></div></div> <div class="sider-box" data-v-377f20d6><h5 class="common-title" data-v-377f20d6>热门文章</h5> <div class="content-box" data-v-377f20d6><div class="hot-article-list" data-v-5fa70b3e><div class="item" data-v-5fa70b3e><a href="/p/6917592658" title="OpenVPN下载、安装、配置及使用详解 " class="abstract multi-ellipsis-2" data-v-5fa70b3e> OpenVPN下载、安装、配置及使用详解 </a></div><div class="item" data-v-5fa70b3e><a href="/p/0907804122" title="GitHub神器,一个可以白嫖全网无损音乐的神器 " class="abstract multi-ellipsis-2" data-v-5fa70b3e> GitHub神器,一个可以白嫖全网无损音乐的神器 </a></div><div class="item" data-v-5fa70b3e><a href="/p/9665141123" title="OpenWrt 路由器过滤广告的N种方法 " class="abstract multi-ellipsis-2" data-v-5fa70b3e> OpenWrt 路由器过滤广告的N种方法 </a></div><div class="item" data-v-5fa70b3e><a href="/p/0868920465" title="Python—执行系统命令的四种方法(os.system、os.popen、commands、subprocess) " class="abstract multi-ellipsis-2" data-v-5fa70b3e> Python—执行系统命令的四种方法(os.system、os.popen、commands、subprocess) </a></div><div class="item" data-v-5fa70b3e><a href="/p/8340561847" title="SS端加密以及obfs混淆 " class="abstract multi-ellipsis-2" data-v-5fa70b3e> SS端加密以及obfs混淆 </a></div></div></div></div></div></div></div> <!----></div></div></div><script>window.__NUXT__=(function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B){return {layout:"default",data:[{}],fetch:{},error:e,state:{showSlideAuth:c,showLogin:c,fromIndex:c,urlConfig:{GET_BLOG_COLLECTION_LIST_URL:j,getAllTag:"\u002Ftag\u002FgetAllTag",CREATE_BLOG_URL:"\u002Fmanage\u002FcreateBlog",PUBLISH_BLOG_URL:"\u002Fmanage\u002FpublishBlog",UPDATE_BLOG_DETAIL:"\u002Fmanage\u002FupdateBlog",GET_BLOG_DETAIL:"\u002Fblog\u002FgetBlogDetail",GET_BLOG_NEWEST_DRAFT:k,UPDATE_CHAPTER_URL:l,GET_CHAPTER_NEWEST_DRAFT:m,PUBLISH_CHAPTER:l,CREATE_BLOG_CATE:"\u002Fcollection\u002Fadd",RENAME_COLLECTION:"\u002Fcollection\u002FrenameCollection",DELETE_COLLECTION:"\u002Fcollection\u002FdeleteCollection",GET_MY_BLOG_LIST_URL:"\u002Fadmin\u002FblogList",FOLLOW_AUTHOR:"\u002Fuser\u002Ffollow",UNFOLLOW_AUTHOR:"\u002Fuser\u002Funfollow",GET_RECOMMEND_BLOG:"\u002Fhome\u002FgetHomeBlogByAction",GET_RECOMMEND_BLOG_BY_CATE:"\u002Fhome\u002FgetHomeBlogListByTag",GET_RECOMMEND_BLOG_BY_TAG:"\u002Fhome\u002FgetBlogListByTag",GET_TAG_LIST:"\u002Fuser\u002FhotWords",GET_CATE_LIST:"\u002Ftag\u002FgetHomeTag",GET_RECOMMEND_AUTHOR:"\u002Fhome\u002FgetRecommendAuthorList",GET_DAILY_ALGORITHM:"\u002Fhome\u002Fdailyalgorithm",LOG_IN:"\u002Faccess\u002Flogin",LOG_OUT:"\u002Faccess\u002FsignOut",CHECK_USER_INFO:"\u002Faccess\u002FcheckUserInfo",REGISTER:"\u002Faccess\u002Fregister",CHANGE_PWD:"\u002Faccess\u002FchangePassword",SEND_CODE:"\u002Faccess\u002FsendCode",ACTION_AUTH:"\u002Faccess\u002FactionAuth",ACTION_AUTH2:"\u002Faccess\u002FactionAuth2",CHECK_USER_AND_BLOG:"\u002Faccess\u002FcheckUserAndBlog",CHECK_TOKEN:"\u002Faccess\u002FcheckToken",SIGN_IN:"\u002Faccess\u002FsignIn",GET_PERSONAL_INFO:"\u002Fuser\u002Fdetail\u002Finfo",GET_PERSONAL_BLOG_LIST:"\u002Fuser\u002Fdetail\u002Fbloglist",FILE_UPLOAD:"\u002Ffile\u002Fupload",DL_AND_UPLOAD:"\u002Ffile\u002FdownloadAndUploadOss",GET_MY_FOLLOW_USER_LIST:"\u002Fadmin\u002FgetMyFollowUserList",GET_MY_FANS_USER_LIST:"\u002Fadmin\u002FgetMyFansUserList",GET_MY_FOLLOW_QUESTION_LIST:"\u002Fadmin\u002FgetMyFollowQuestion",GET_MY_SUBSCIBE_SPECIAL_LIST:"\u002Fadmin\u002FgetMySubscribeSpecialList",TAKE_OFF_BLOG:"\u002Fadmin\u002FtakeOffBlog",TAKE_ON_BLOG:"\u002Fadmin\u002FtakeOnBlog",GET_HOME_RECOMMEND_SPECIAL_LIST:"\u002Fhome\u002FgetRecommendSpecialList",GET_MOST_SPECIAL_COUNT_USER_LIST:"\u002Fspecial\u002FgetSpecialMostUserList",GET_SPECIAL_CATE_LIST_URL:"\u002Fspecial\u002FgetSpecialCateList",GET_SPECIAL_LIST_URL:"\u002Fspecial\u002FgetSpecialList",GET_SPECIAL_LIST_PER_CATEGORY_URL:"\u002Fspecial\u002FgetSpecialListPerCategory",GET_CATEGORY_INFO_BY_ID_URL:"\u002Fhome\u002FgetCategoryInfoById",GET_SPECIAL_COUNT_PER_CATEGORY_URL:"\u002Fspecial\u002FgetSpecialTotalCountPerCategory",GET_SPECIAL_BANNER:"\u002Fspecial\u002FgetBannerList",GET_SPECIAL_DETAIL_CHAPTER_LIST:"\u002Fspecial\u002FgetSpecialSectionList",GET_SPECIAL_DETAIL:"\u002Fspecial\u002FgetMySpecialChapterInfo",GetChapterInfo:"\u002Fspecial\u002FgetSectionDetail",GET_SPECIAL_DETAIL_INFO:"\u002Fadmin\u002FgetSpecialDetail",ADD_CHAPTER_URL:"\u002Fadmin\u002FaddChapter",UPDATE_CHAPTER_INFO:"\u002Fadmin\u002FupdateChapterInfo",UPDATE_CHAPTER_SEQUENCE:"\u002Fadmin\u002FupdateChapterSequence",DELETE_CHAPTER_URL:"\u002Fadmin\u002FdeleteChapter",TAKE_ON_SPECIAL:"\u002Fspecial\u002FpublishSection",TAKE_OFF_SPECIAL:"\u002Fadmin\u002FtakeOffSpecial",RENAME_SPECIAL:"\u002Fadmin\u002FrenameSpecial",GET_SPECIAL_CATE_LIST:"\u002Fadmin\u002FgetSpecialCateList",UPDATE_SPECIAL_INTRO:"\u002Fadmin\u002FupdateSpecialIntro",UPDATE_SPECIAL_INFO:"\u002Fadmin\u002FupdateSpecialCateId",UPDATE_SPECIAL_TITLE:"\u002Fadmin\u002FupdateSpecialTitle",DELETE_SPECIAL:"\u002Fadmin\u002FdeleteSpecial",GET_CHAPTER_COMMENT:"\u002Fcomment\u002FgetChapterCommentList",UPDATE_SPECIALINFO:"\u002Fadmin\u002FupdateSpecialBaseInfo",SUBSCRIBE_SPECIAL:n,UN_SUBSCRIBE_SPECIAL:o,MODIFY_SPECIAL:p,GET_TUTORIAL_BIG_CATEGORY:"\u002Ftutorial\u002FgetTutorialBigCategories",GET_ALL_TUTORIALS:q,GET_CHAPTER_AND_SECTION_BY_PATH:"\u002Ftutorial\u002FgetChapterAndSectionByPath",GET_CONTENT_BY_SECTION_PATH:"\u002Ftutorial\u002FgetSectionContent",GET_TUTORIAL_OVERVIEW:r,GET_UID_BY_PROFILE_PATH:"\u002Fuser\u002FgetUidByProfilePath",GET_USER_DETAIL_USER_INFO:"\u002Fuser\u002Fdetail\u002FuserInfo",GET_SPECIAL_CATEGORY:"\u002Fuser\u002Fdetail\u002FspecialCategory",CREATE_SPECIAL_URL:"\u002Fspecial\u002FcreateSpecial",GET_MY_SPECIAL_LIST_URL:"\u002Fadmin\u002FgetMySpecialList",GET_CHAPTER_LIST_PER_SPECIAL_URL:"\u002Fadmin\u002FgetSpecialChapterList",GET_USER_RELATED_QUESTION_LIST:"\u002Fuser\u002Fdetail\u002FgetUserRelatedQuestionList",Get_BLOG_COLLECT_LIST:"\u002Fuser\u002Fdetail\u002FgetUserBlogCollectList",GET_BLOG_LIST_URL:"\u002Fuc\u002FgetUserBlogList",GET_BLOG_LIST_BY_COLLECT_ID:"\u002Fuser\u002Fdetail\u002FgetCollectBlogList",GET_SPECIAL_LIST_BY_SORT_TYPE:"\u002Fuc\u002FgetUserPublishedSpecialList",GET_USER_BASE_INFO:"\u002Fuc\u002FgetUserInfo",GET_NEWEST_BLOG_LIST:"\u002Fuser\u002Fdetail\u002FgetNewestBlogList",GET_NEWEST_COMMENT_LIST:"\u002Fuser\u002Fdetail\u002FgetNewestCommentList",GET_FAVORITE_BLOG_LIST:"\u002Fuc\u002FgetUserFavoriteBlogList",GET_FOLLOW_USER_LIST:"\u002Fuc\u002FgetFollowList",GET_FOLLOW_USER_FANS:"\u002Fuc\u002FgetFanList",GET_HOT_COMMENTS:"\u002Fuc\u002FgetHotComments",IS_USER_BLOG:"\u002Fuc\u002FisUserBlog",GET_SEARCH_WORD:"\u002Fsearch\u002FsearchWord",GET_QUESTION_COMMON_TAG:"\u002Fquestion\u002FgetHotTagList",GET_QUESTION_LIST:"\u002Fquestion\u002FgetQuestionList",GET_QUESTION_DETAIL:"\u002Fquestion\u002FgetQuestionDetail",UPDATE_QUESTION_INFO:"\u002Fquestion\u002FupdateQuestion",GET_QUESTION_BASE_INFO:"\u002Fquestion\u002FgetQuestionBaseInfo",GET_BLOG_COMMENT_LIST:"\u002Fcomment\u002FgetBlogCommentList",ADD_COMMENT:"\u002Fcomment\u002FaddComment",ADD_REPLY:"\u002Fcomment\u002FaddReply",LIKE_BLOG:"\u002Fblog\u002FzanBlog",FAVORITE_BLOG:"\u002Fblog\u002FfavoriteBlog",GET_USER_LIKE_FAVORITE_INFO:"\u002Fadmin\u002FgetUserAndBlogActionInfo",DAMIT_CHANGE_PWD:"\u002Fadmin\u002FchangePassword",UPDATE_PERSONAL:"\u002Fadmin\u002FmodifyPersonalInfo",SUBMIT_SUGGESTION:"\u002Fsuggestion\u002FsubmitSuggestion",GETALLBLOGID:"\u002Faccess\u002FgetAllBlogId",GETCONTENTBYID:"\u002Faccess\u002FgetContentById",UPDATEHTMLBYID:"\u002Faccess\u002FupdateHtmlById",CHANGE_USER_PWD:s,SEND_USER_SMS:"\u002Faccess\u002FsendSmsCode",GET_WIN_USERS:"\u002Fwin\u002FgetWinUsers",GET_FRIEND_LINKS:"\u002Fhome\u002FgetFriendLinks",GET_SITE_MAP:"\u002Fspider\u002FgetSiteMap",GET_BLOG_BY_TAG_NAME:"\u002Fblog\u002FgetBlogByTagName",GET_BLOG_BY_TAG_ID:"\u002Fsearch\u002FsearchTag",GET_RANDOM_TAGS:"\u002Fblog\u002FgetRandomTags",GET_UPLOAD_SIGNATURE:"\u002Fvod\u002FgetUploadSignature",UPLOAD_MEDIA_INFO:"\u002Fvod\u002FuploadMediaInfo",GET_BLOG_INFO:"\u002Fadmin\u002FgetBlogInfo",GET_HOME_RECOMMEND_TAGS:"\u002Ftag\u002FgetHomeRecommendTags",GET_USER_HOT_BLOG_LIST:"\u002Fuc\u002FgetUserHotBlogList",COMMENT_BLOG:"\u002Fcomment\u002Fcomment",GET_COMMENT_LIST:"\u002Fcomment\u002FgetCommentList",REPLY_COMMENT:"\u002Fcomment\u002FreplyComment",GET_COMMENT_REPLIES:"\u002Fcomment\u002FgetCommentReplies",GET_BACK_IMAGE_LIST:"\u002Fuc\u002FgetBackImageList",UPDATE_BACK_IMAGE:"\u002Fuc\u002FupdateBackImage",GET_MY_SPECIAL_LIST:"\u002Fmanage\u002FgetMySpecialList",GET_SUBCRIBED_SPECLIAL_LIST:"\u002Fmanage\u002FgetMySubscribedSpecialList",GET_MY_SPECIAL_DETAIL:"\u002Fmanage\u002FgetMySpecialDetail",SUBCRIBE_SPECLIAL:n,UNSUBCRIBED_SPECLIAL:o,CREATE_COLLECTION:"\u002Fmanage\u002FcreateCollection",GET_COLLECTION_LIST:j,UPDATE_COLLECTION:"\u002Fmanage\u002FupdateCollection",REMOVE_COLLECTION:"\u002Fmanage\u002FremoveCollection",GET_MY_BLOG_LIST:"\u002Fmanage\u002FgetMyBlogList",GET_FOLLOW_LIST:"\u002Fmanage\u002FgetFollowList",UPDATE_FOLLOW:"\u002Fmanage\u002FupdateFollow",GET_MY_INFO:"\u002Fmanage\u002FgetMyInfo",UPDATE_MY_INFO:"\u002Fmanage\u002FupdateMyInfo",GET_MY_FAVORITE_BLOG_LIST:"\u002Fmanage\u002FgetMyFavoriteBlogList",GET_MY_FAVORITE_LESSON_LIST:"\u002Flesson\u002FfavoriteList",UPDATE_FAVORITE_LESSON:"\u002Flesson\u002Ffavorite",UPDATE_CANCEL_FAVORITE_LESSON:"\u002Flesson\u002FremoveFavorite",GET_RECOMMEND_LESSON_BY_BLOG:"\u002Flesson\u002FrecommendLesson",MODIFY_PASSWORD:s,GET_MY_BLOG_DETAIL:k,GET_MY_BLOG_DETAIL_FOR_ADMIN_PREVIEW:"\u002Fmanage\u002FgetMyBlogDetailForAdminPreview",GET_NEW_NOTIFICATION_COUNT:"\u002Fmanage\u002FgetNewNotificationCount",GET_NOTIFICATION_LIST:"\u002Fmanage\u002FgetNotificationList",READ_NOTIFICATION:"\u002Fmanage\u002FreadNotification",CREATE_SECTION:"\u002Fspecial\u002FcreateSection",MODIFIY_SECTION_TITLE:"\u002Fspecial\u002FmodifySectionTitle",MODIFIY_SPECIAL_TITLE:p,MODIFIY_SECTION_STATUS:"\u002Fspecial\u002FmodifySectionStatus",PUBLISH_SPECIAL:"\u002Fspecial\u002FpublishSpecial",OFFLINE_SPECIAL:"\u002Fspecial\u002FofflineSpecial",UNPUBLISHED_SPECIAL_DETAIL:"manage\u002FgetMySpecialDetail",UNPUBLISHED_SPECIAL_CHAPTER_DETAIL:m,UPDATE_BLOG_TITLE:"\u002Fmanage\u002FupdateTitle",UPDATE_BLOG_STATUS_PUBLISHED:"\u002Fmanage\u002FupdateBlogStatusPublished",DELETE_BLOG:"\u002Fmanage\u002FdeleteBlog",GET_CATELIST_TUTORIAL:"\u002Ftutorial\u002FgetCateList",GET_TUTORIAL_LIST:q,GET_TUTORIAL_DETAIL:r,GET_CHAPTER_INFO:"\u002Ftutorial\u002FgetChapterInfo",FAVORITE_SECTION:"\u002Fmanage\u002FfavoriteSection",ZAN_SECTION:"\u002Fmanage\u002FzanSection",LESSON_LIST_ALL:"\u002Flesson\u002Flist\u002Fall",LESSON_LIST_MY:"\u002Flesson\u002Flist\u002Fmy",LESSON_LIST_BUY:"\u002Flesson\u002Flist\u002Fbuy",LESSON_LIST_REC:"\u002Flesson\u002Flist\u002Frec",LESSON_CREATE:"\u002Flesson\u002Fcreate",LESSON_DETAIL_EDIT:"\u002Flesson\u002Fdetail\u002Fedit",LESSON_DETAIL:"\u002Flesson\u002Fdetail",LESSON_DELETE:"\u002Flesson\u002FdeleteLesson",LESSON_CATEGORIES:"\u002Flesson\u002Fcategories",LESSON_DIRECTIONS:"\u002Flesson\u002Fdirections",LESSON_STORE:"\u002Flesson\u002Fstore",LESSON_CREATE_CHAPTER:"\u002Flesson\u002FcreateChapter",LESSON_UPDATE_CHATPER:"\u002Flesson\u002FupdateChapter",LESSON_DELETE_CHAPTER:"\u002Flesson\u002FdeleteChapter",LESSON_CHAPTER_DELETE_VIDEO:"\u002Flesson\u002Fchapter\u002FdeleteVideo",LESSON_CHAPTER_UPDATE_VIDEO:"\u002Flesson\u002Fchapter\u002FupdateVideo",LESSON_CHAPTER_CREATE_VIDEO:"\u002Flesson\u002Fchapter\u002FcreateVideo",LESSON_CHAPTERS:"\u002Flesson\u002Fchapters",LESSON_CHAPTERS_EDIT:"\u002Flesson\u002Fchapters\u002Fedit",LESSON_VIDEO_ENCRYPT:"\u002Flesson\u002Fvideo\u002Fencrypt",LESSON_VIDEO:"\u002Flesson\u002Fvideo",LESSON_VIDEO_M3U8:"\u002Flesson\u002Fvideo\u002Fm3u8",LESSON_COS_TOKEN:"\u002Flesson\u002Fcos\u002Ftoken",LESSON_NOTE:"\u002Flesson\u002Fnote",LESSON_CREATE_SOURCE:"\u002Flesson\u002FcreateResource",LESSON_UPDATE_RESOURCE:"\u002Flesson\u002FupdateResource",LESSON_RESOURCE:"\u002Flesson\u002Fresource",LESSON_DELETE_RESOURCE:"\u002FlessondeleteResource",LESSON_LEARN_REPORT:"\u002Flesson\u002Flearn\u002Freport",ADD_STUDY_COUNT:"\u002Flesson\u002FaddStudyCount",WXPAY_NATIVE_PAY:"\u002Fwxpay\u002FnativePay",WXPAY_QUERY_ORDER_STATUS:"\u002Fwxpay\u002FqueryOrderStatus",WXPAY_ORDER:"\u002Fwxpay\u002Forder",WXPAY_ORDERS:"\u002Fwxpay\u002Forders",WXPAY_CANCELORDER:"\u002Fwxpay\u002FcancelOrder"},isLoading:c,authPhone:b,friendLink:[],showSpecial:c,specialData:e,navFixedVisible:c,deviceId:360620123720643900,blackList:[],access:{token:b,userInfo:{}},admin:{collectionList:[],specialList:[],notifyCount:{},curTab:"blog",blogDetail:{}},backstage:{blogList:{},typeCount:{collectCount:a,commentCount:a,count:a,followCount:a,subscribeCount:a,zanCount:a},isShow:c},course:{isCollapase:t},personal:{errorMsg:b,handling:c,visible:c,success:c,isSmsCoding:c,sendCodeSuccess:c,uid:e,userDetailInfo:{},personalBlogList:[],blogCount:a,personalSpecialList:[],specialCount:a,newestBlog:[],newestComment:[],personalFavoriteList:[],favoriteCount:a,personalFollowList:[],followCount:a,personalFansList:[],fansCount:a,hotComment:[],routeParams:{profile:b,pageType:b}},question:{commonTags:[],questionList:[],questionDetail:{}},recommend:{recommendBlog:{data:[]},tagList:[],categoryList:{data:[]},recommendAuthorList:{data:[]},todayAlgorithm:b,blogDetail:{blogInfo:{uuid:"2962064446",title:"JavaScript异步编程解决方案 ",intro:"Generator函数学习指导:阮一峰ES6Generator函数(https:\u002F\u002Fwww.oschina.net\u002Faction\u002FGoToLink?urlhttp%3A%2F%2Fes6.ruanyifeng.com%2F%23docs%2Fgenerator)基本概念语法上:Genera",content:"Generator 函数\n------------\n\n学习指导:[阮一峰ES6 -- Generator函数](https:\u002F\u002Fwww.oschina.net\u002Faction\u002FGoToLink?url=http%3A%2F%2Fes6.ruanyifeng.com%2F%23docs%2Fgenerator)\n\n### 基本概念\n\n* 语法上:Generator 函数是一个状态机,封装了多个内部状态;执行 Generator 函数会返回一个遍历器对象。也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。\n* 形式上:Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号`*`;二是,函数体内部使用`yield`表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。\n\n function* helloWorldGenerator() {\n yield 'hello';\n yield 'world';\n return 'ending';\n }\n var hw = helloWorldGenerator();\n \n \u002F\u002F 定义了一个 Generator 函数helloWorldGenerator,它内部有两个yield表达式(hello和world)\n \u002F\u002F 即该函数有三个状态:hello,world 和 return 语句(结束执行)。\n \n\n* 调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后,每次调用遍历器对象的`next`方法,就会返回一个有着`value和done`两个属性的对象。`value`属性表示当前的内部状态的值,是`yield`表达式后面那个表达式的值;`done`属性是一个布尔值,表示是否遍历结束。\n\n \u002F\u002F 上述函数下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。\n \u002F\u002F 即每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,\n \u002F\u002F 直到遇到下一个yield表达式(或return语句)为止。\n \u002F\u002F 换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。\n \n hw.next()\n \u002F\u002F { value: 'hello', done: false }\n hw.next()\n \u002F\u002F { value: 'world', done: false }\n hw.next()\n \u002F\u002F { value: 'ending', done: true }\n hw.next()\n \u002F\u002F { value: undefined, done: true }\n \n \u002F*\n 解释说明:\n 上面代码一共调用了四次next方法。\n 第一次调用,Generator 函数开始执行,直到遇到第一个yield表达式为止。\n next方法返回一个对象,它的value属性就是当前yield表达式的值hello,\n done属性的值false,表示遍历还没有结束。\n \n 第二次调用,Generator 函数从上次yield表达式停下的地方,一直执行到下一个yield表达式。\n next方法返回的对象的value属性就是当前yield表达式的值world,\n done属性的值false,表示遍历还没有结束。\n \n 第三次调用,Generator 函数从上次yield表达式停下的地方,一直执行到return语句\n (如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性,\n 就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),\n done属性的值true,表示遍历已经结束。\n \n 第四次调用,此时 Generator 函数已经运行完毕,\n next方法返回对象的value属性为undefined,done属性为true。\n 以后再调用next方法,返回的都是这个值。\n *\u002F\n \n\n### yield 表达式说明\n\n* Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。\n* 遍历器对象的next方法的运行逻辑如下。\n * 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。\n * 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。\n * 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。\n * 如果该函数没有return语句,则返回的对象的 value 属性值为 undefined。\n* 需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。\n\n function* gen(){\n yield 123 + 456;\n }\n \n \u002F\u002F yield后面的表达式123 + 456,不会立即求值,只会在next方法将指针移到这一句时,才会求值.\n \n\n### yield 表达式语法\n\n* yield表达式只能用在 Generator 函数里面,用在其他地方都会报错\n\n (function (){\n yield 1;\n })()\n \u002F\u002F SyntaxError: Unexpected number\n \n\n* yield表达式如果用在另一个表达式之中,必须放在圆括号里面\n\n function* demo() {\n console.log('Hello' + yield); \u002F\u002F SyntaxError\n console.log('Hello' + yield 123); \u002F\u002F SyntaxError\n \n console.log('Hello' + (yield)); \u002F\u002F OK\n console.log('Hello' + (yield 123)); \u002F\u002F OK\n }\n \n\n* yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号\n\n function* demo() {\n foo(yield 'a', yield 'b'); \u002F\u002F OK\n let input = yield; \u002F\u002F OK\n }\n \n\n### 用法实例讲解\n\n var arr = [1, [[2, 3], 4], [5, 6]];\n \n var flat = function* (a) {\n a.forEach(function (item) {\n if (typeof item !== 'number') {\n yield* flat(item);\n } else {\n yield item;\n }\n });\n }; \u002F\u002F 错误用法\n \u002F\u002F forEach方法的参数是一个普通函数,但是在里面使用了yield表达式\n \n \u002F\u002F 一种修改方法是改用for循环。\n var flat = function* (a) {\n var length = a.length;\n for (var i = 0; i \u003C length; i++) {\n var item = a[i];\n if (typeof item !== 'number') {\n yield* flat(item);\n } else {\n yield item;\n }\n }\n };\n \n for (var f of flat(arr)) {\n console.log(f);\n }\n \u002F\u002F 1, 2, 3, 4, 5, 6\n \n\n### yield表达式与return语句\n\n* yield 表达式与return 语句既有相似之处,也有区别。\n* 相似之处在于,都能返回紧跟在语句后面的那个表达式的值。\n* 区别在于每次遇到`yield`,函数暂停执行,下一次再从该位置继续向后执行,而`return`语句不具备位置记忆的功能。\n* 一个函数里面,**只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield表达式**。\n* 正常函数只能返回一个值,因为只能执行一次return;\n* Generator 函数可以返回一系列的值,因为可以有任意多个`yield`。\n* 从另一个角度看,也可以说 Generator 生成了一系列的值。\n\n### next 方法的参数\n\n* `yield`表达式本身没有返回值,或者说总是返回`undefined`。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。\n\n function* f() {\n for(var i = 0; true; i++) {\n var reset = yield i;\n if(reset) { i = -1; }\n }\n }\n var g = f();\n \n g.next() \u002F\u002F { value: 0, done: false }\n g.next() \u002F\u002F { value: 1, done: false }\n g.next(true) \u002F\u002F { value: 0, done: false }\n \n \u002F*\n 上面代码先定义了一个可以无限运行的 Generator 函数f,\n 如果next方法没有参数,每次运行到yield表达式,变量reset的值总是undefined。\n \n 当next方法带一个参数true时,变量reset就被重置为这个参数(即true),因此i会等于-1,\n 下一轮循环就会从-1开始递增。\n *\u002F\n \n\n* 这个功能有很重要的语法意义。Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。\n* 通过next方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值。也就是说,可以在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。\n\n function* foo(x) {\n var y = 2 * (yield (x + 1));\n var z = yield (y \u002F 3);\n return (x + y + z);\n }\n \n var a = foo(5);\n a.next() \u002F\u002F Object{value:6, done:false}\n a.next() \u002F\u002F Object{value:NaN, done:false}\n a.next() \u002F\u002F Object{value:NaN, done:true}\n \n var b = foo(5);\n b.next() \u002F\u002F { value:6, done:false }\n b.next(12) \u002F\u002F { value:8, done:false }\n b.next(13) \u002F\u002F { value:42, done:true }\n \n \u002F*\n 上面代码中,第二次运行next方法的时候不带参数,导致 y 的值等于2 * undefined(即NaN),\n 除以 3 以后还是NaN,因此返回对象的value属性也等于NaN。\n 第三次运行Next方法的时候不带参数,所以z等于undefined,\n 返回对象的value属性等于5 + NaN + undefined,即NaN。\n \n 如果向next方法提供参数,返回结果就完全不一样了。\n 上面代码第一次调用b的next方法时,返回x+1的值6;\n 第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y \u002F 3的值8;\n 第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,\n 这时x等于5,y等于24,所以return语句的值等于42。\n *\u002F\n \n\n* 注意,由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。\n\n### for...of 循环\n\n* 1. for...of循环可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法。\n\n \n function* foo() {\n yield 1;\n yield 2;\n yield 3;\n yield 4;\n yield 5;\n return 6;\n }\n for (let v of foo()) {\n console.log(v);\n }\n \u002F\u002F 1 2 3 4 5\n \n \u002F\u002F 上面代码使用for...of循环,依次显示 5 个yield表达式的值。\n \u002F\u002F 这里需要注意,一旦next方法的返回对象的done属性为true,\n \u002F\u002F for...of循环就会中止,且不包含该返回对象,\n \u002F\u002F 所以上面代码的return语句返回的6,不包括在for...of循环之中。\n \n\n* 2)利用`for...of`循环,可以写出遍历任意对象(object)的方法。原生的 JavaScript 对象没有遍历接口,无法使用for...of循环,通过 Generator 函数为它加上这个接口,就可以用了。\n\n function* objectEntries(obj) {\n let propKeys = Reflect.ownKeys(obj);\n \n for (let propKey of propKeys) {\n yield [propKey, obj[propKey]];\n }\n }\n \n let jane = { first: 'Jane', last: 'Doe' };\n \n for (let [key, value] of objectEntries(jane)) {\n console.log(`${key}: ${value}`);\n }\n \u002F\u002F first: Jane\n \u002F\u002F last: Doe\n \n\n* 3)加上遍历器接口的另一种写法是,将 Generator 函数加到对象的`Symbol.iterator`属性上面\n\n function* objectEntries() {\n let propKeys = Object.keys(this);\n \n for (let propKey of propKeys) {\n yield [propKey, this[propKey]];\n }\n }\n \n let jane = { first: 'Jane', last: 'Doe' };\n \n jane[Symbol.iterator] = objectEntries;\n \n for (let [key, value] of jane) {\n console.log(`${key}: ${value}`);\n }\n \u002F\u002F first: Jane\n \u002F\u002F last: Doe\n \n\n* 4)除了for...of循环以外,`扩展运算符(...)`、`解构赋值`和`Array.from`方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。\n\n function* numbers () {\n yield 1\n yield 2\n return 3\n yield 4\n }\n \n \u002F\u002F 扩展运算符\n [...numbers()] \u002F\u002F [1, 2]\n \n \u002F\u002F Array.from 方法\n Array.from(numbers()) \u002F\u002F [1, 2]\n \n \u002F\u002F 解构赋值\n let [x, y] = numbers();\n x \u002F\u002F 1\n y \u002F\u002F 2\n \n \u002F\u002F for...of 循环\n for (let n of numbers()) {\n console.log(n)\n }\n \u002F\u002F 1\n \u002F\u002F 2\n \n\n### Generator函数的异步操作\n\n function* asyncJob() {\n \u002F\u002F ...其他代码\n var f = yield readFile(fileA);\n \u002F\u002F ...其他代码\n }\n \n\n函数asyncJob是一个协程,它的奥妙就在其中的yield命令。它表示执行到此处,执行权将交给其他协程。 也就是说,yield命令是异步两个阶段的分界线。 协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作,如果去除yield命令,简直一模一样。\n\nGenerator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。 \n整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用yield语句注明。\n\n### 异步任务的封装\n\n var fetch = require('node-fetch');\n \n function* gen(){\n var url = 'https:\u002F\u002Fapi.github.com\u002Fusers\u002Fgithub';\n var result = yield fetch(url);\n console.log(result.bio);\n }\n \n \u002F\u002F Generator 函数封装了一个异步操作,该操作先读取一个远程接口,然后从 JSON 格式的数据解析信息。\n \u002F\u002F 就像前面说过的,这段代码非常像同步操作,除了加上了yield命令。\n \n \u002F\u002F 执行上述代码\n var g = gen();\n var result = g.next();\n \n result.value.then(function(data){\n return data.json();\n }).then(function(data){\n g.next(data);\n });\n \n \u002F\u002F 首先执行 Generator 函数,获取遍历器对象,然后使用next方法(第二行),执行异步任务的第一阶段。\n \u002F\u002F 由于Fetch模块返回的是一个 Promise 对象,因此要用then方法调用下一个next方法。\n \n\n虽然 Generator 函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。\n\nasync 函数\n--------\n\n学习指导:[阮一峰ES6 -- async函数](https:\u002F\u002Fwww.oschina.net\u002Faction\u002FGoToLink?url=http%3A%2F%2Fes6.ruanyifeng.com%2F%23docs%2Fasync)\n\n### 含义\n\n* async 函数是什么?一句话,它就是 Generator 函数的语法糖\n\n \u002F\u002F generator 函数依次读取两个文件\n const fs = require('fs');\n const readFile = function (fileName) {\n return new Promise(function (resolve, reject) {\n fs.readFile(fileName, function(error, data) {\n if (error) return reject(error);\n resolve(data);\n });\n });\n };\n const gen = function* () {\n const f1 = yield readFile('\u002Fetc\u002Ffstab');\n const f2 = yield readFile('\u002Fetc\u002Fshells');\n console.log(f1.toString());\n console.log(f2.toString());\n };\n \n \u002F\u002F async函数,就是下面这样\n const asyncReadFile = async function () {\n const f1 = await readFile('\u002Fetc\u002Ffstab');\n const f2 = await readFile('\u002Fetc\u002Fshells');\n console.log(f1.toString());\n console.log(f2.toString());\n };\n \n \u002F\u002F async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。\n \n\n* async函数对 Generator 函数的改进,体现在以下四点:\n * **内置执行器** —— Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。\n * **更好的语义** —— async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。\n * **更广的适用性** —— co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。\n * **返回值是 Promise** —— async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。**进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。**\n\n### 基本用法\n\nasync函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。\n\n* 引入demo\n\n \u002F* async 表示异步调用 返回一个Promise对象 *\u002F\n async function timeout(ms) {\n await new Promise((resolve) =\u003E {\n setTimeout(resolve, ms);\n });\n } \u002F* 与下边等价*\u002F\n \n \u002F\u002F function timeout(ms) {\n \u002F\u002F return new Promise((resolve) =\u003E {\n \u002F\u002F setTimeout(resolve, ms);\n \u002F\u002F });\n \u002F\u002F }\n \n async function asyncPrint(value, ms) {\n await timeout(ms);\n console.log(value);\n }\n \n asyncPrint('hello world', 3000) \u002F\u002F 3000毫秒以后,输出hello world\n \n\n* async的表达方式\n\n \n \u002F\u002F 函数声明\n async function foo() {}\n \n \u002F\u002F 函数表达式\n const foo = async function () {};\n \n \u002F\u002F 对象的方法\n let obj = { async foo() {} };\n obj.foo().then(...)\n \n \u002F\u002F Class 的方法\n class Storage {\n constructor() {\n this.cachePromise = caches.open('avatars');\n }\n \n async getAvatar(name) {\n const cache = await this.cachePromise;\n return cache.match(`\u002Favatars\u002F${name}.jpg`);\n }\n }\n \n const storage = new Storage();\n storage.getAvatar('jake').then(…);\n \n \u002F\u002F 箭头函数\n const foo = async () =\u003E {};\n \n\n### async 的语法\n\n* 返回Promise 对象\n \n * async函数返回一个 Promise 对象。\n * async函数内部return语句返回的值,会成为then方法回调函数的参数。\n \n async function f() {\n return 'hello world';\n }\n \n f().then(v =\u003E console.log(v))\n \u002F\u002F \"hello world\"\n \u002F\u002F 函数 f 内部return命令返回的值,会被then方法回调函数接收到。\n \n \n* async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。\n \n\n async function f() {\n throw new Error('出错了');\n }\n \n f().then(\n v =\u003E console.log(v),\n e =\u003E console.log(e)\n )\n \u002F\u002F Error: 出错了\n \n\n* Promise 对象的状态变化\n \n * async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。\n * 也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。\n \n async function getTitle(url) {\n let response = await fetch(url);\n let html = await response.text();\n return html.match(\u002F\u003Ctitle\u003E([\\s\\S]+)\u003C\\\u002Ftitle\u003E\u002Fi)[1];\n }\n getTitle('https:\u002F\u002Ftc39.github.io\u002Fecma262\u002F').then(console.log)\n \u002F\u002F \"ECMAScript 2017 Language Specification\"\n \n \u002F\u002F 函数getTitle内部有三个操作:抓取网页、取出文本、匹配页面标题。\n \u002F\u002F 只有这三个操作全部完成,才会执行then方法里面的 console.log。\n \n \n* await 命令\n \n * 正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。\n \n async function f() {\n return await 123;\n }\n \n f().then(v =\u003E console.log(v))\n \u002F\u002F 123\n \n \u002F\u002F await命令的参数是数值123,它被转成 Promise 对象,并立即resolve\n \n \n * await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。\n \n async function f() {\n await Promise.reject('出错了');\n }\n \n f()\n .then(v =\u003E console.log(v))\n .catch(e =\u003E console.log(e))\n \u002F\u002F 出错了\n \n \u002F\u002F await 语句前面没有 return,但是 reject 方法的参数依然传入了 catch 方法的回调函数。\n \u002F\u002F 这里如果在await前面加上return,效果是一样的。\n \n \n * 只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行\n \n async function f() {\n await Promise.reject('出错了');\n await Promise.resolve('hello world'); \u002F\u002F 不会执行\n }\n \n \n * 可以将第一个await放在`try...catch`结构里面,这样不管这个异步操作是否成功,第二个await都会执行。\n \n async function f() {\n try {\n await Promise.reject('出错了');\n } catch(e) {\n }\n return await Promise.resolve('hello world');\n }\n \n f()\n .then(v =\u003E console.log(v))\n \u002F\u002F hello world\n \n \n * 另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误\n \n async function f() {\n await Promise.reject('出错了')\n .catch(e =\u003E console.log(e));\n return await Promise.resolve('hello world');\n }\n \n f()\n .then(v =\u003E console.log(v))\n \u002F\u002F 出错了\n \u002F\u002F hello world\n \n \n\n### 使用注意点\n\n* 第一点,前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在`try...catch`代码块中。\n* 第二点,多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。\n\n let foo = await getFoo();\n let bar = await getBar();\n \u002F\u002F 上面代码中,getFoo和getBar是两个独立的异步操作(即互不依赖),被写成继发关系。\n \u002F\u002F 这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。\n \n \n \u002F\u002F 写法一\n let [foo, bar] = await Promise.all([getFoo(), getBar()]);\n \u002F\u002F 写法二\n let fooPromise = getFoo();\n let barPromise = getBar();\n let foo = await fooPromise;\n let bar = await barPromise;\n \u002F\u002F getFoo和getBar都是同时触发,这样就会缩短程序的执行时间\n \n\n* 第三点,await命令只能用在async函数之中,如果用在普通函数,就会报错。\n\n async function dbFuc(db) {\n let docs = [{}, {}, {}];\n \n \u002F\u002F 报错\n docs.forEach(function (doc) {\n await db.post(doc);\n });\n }\n \u002F\u002F 上面代码会报错,因为await用在普通函数之中了\n \n \u002F\u002F 采用for循环\n async function dbFuc(db) {\n let docs = [{}, {}, {}];\n \n for (let doc of docs) {\n await db.post(doc);\n }\n }\n \n \u002F\u002F 如果确实希望多个请求并发执行,可以使用Promise.all方法。\n \u002F\u002F 当三个请求都会resolved时,下面两种写法效果相同。\n async function dbFuc(db) {\n let docs = [{}, {}, {}];\n let promises = docs.map((doc) =\u003E db.post(doc));\n \n let results = await Promise.all(promises);\n console.log(results);\n }\n \n \u002F\u002F 或者使用下面的写法\n async function dbFuc(db) {\n let docs = [{}, {}, {}];\n let promises = docs.map((doc) =\u003E db.post(doc));\n \n let results = [];\n for (let promise of promises) {\n results.push(await promise);\n }\n console.log(results);\n }\n \n\n### async函数实现原理\n\n* async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里\n\n async function fn(args) {\n \u002F\u002F ...\n }\n \n \u002F\u002F 等同于\n function fn(args) {\n return spawn(function* () {\n \u002F\u002F ...\n });\n }\n \n \u002F\u002F 所有的async函数都可以写成上面的第二种形式,其中的spawn函数就是自动执行器。\n \n\n### 场景再现\n\n* 假定某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值。\n\n async function chainAnimationsAsync(elem, animations) {\n let ret = null; \u002F\u002F 变量ret用来保存上一个动画的返回值\n try {\n for(let anim of animations) {\n ret = await anim(elem);\n }\n } catch(e) {\n \u002F* 忽略错误,继续执行 *\u002F\n }\n return ret;\n }\n \n\n* 依次远程读取一组 URL,然后按照读取的顺序输出结果。\n\n \u002F\u002F 远程操作继发.只有前一个 URL 返回结果,才会去读取下一个 URL,这样做效率很差,非常浪费时间\n async function logInOrder(urls) {\n for (const url of urls) {\n const response = await fetch(url);\n console.log(await response.text());\n }\n }\n \n \u002F\u002F 并发发出远程请求\n async function logInOrder(urls) {\n \u002F\u002F 并发读取远程URL\n const textPromises = urls.map(async url =\u003E {\n const response = await fetch(url);\n return response.text();\n });\n \n \u002F\u002F 按次序输出\n for (const textPromise of textPromises) {\n console.log(await textPromise);\n }\n }",html:"\u003Ch2 id=\"generator-函数\"\u003EGenerator 函数\u003C\u002Fh2\u003E\n\u003Cp\u003E学习指导:\u003Ca href=\"https:\u002F\u002Fwww.oschina.net\u002Faction\u002FGoToLink?url=http%3A%2F%2Fes6.ruanyifeng.com%2F%23docs%2Fgenerator\"\u003E阮一峰ES6 -- Generator函数\u003C\u002Fa\u003E\u003C\u002Fp\u003E\n\u003Ch3 id=\"基本概念\"\u003E基本概念\u003C\u002Fh3\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cp\u003E语法上:Generator 函数是一个状态机,封装了多个内部状态;执行 Generator 函数会返回一个遍历器对象。也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003E形式上:Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号\u003Ccode\u003E*\u003C\u002Fcode\u003E;二是,函数体内部使用\u003Ccode\u003Eyield\u003C\u002Fcode\u003E表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。\u003C\u002Fp\u003E\n\u003Cp\u003Efunction* helloWorldGenerator() {\n yield 'hello';\n yield 'world';\n return 'ending';\n}\nvar hw = helloWorldGenerator();\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 定义了一个 Generator 函数helloWorldGenerator,它内部有两个yield表达式(hello和world)\n\u002F\u002F 即该函数有三个状态:hello,world 和 return 语句(结束执行)。\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003E调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后,每次调用遍历器对象的\u003Ccode\u003Enext\u003C\u002Fcode\u003E方法,就会返回一个有着\u003Ccode\u003Evalue和done\u003C\u002Fcode\u003E两个属性的对象。\u003Ccode\u003Evalue\u003C\u002Fcode\u003E属性表示当前的内部状态的值,是\u003Ccode\u003Eyield\u003C\u002Fcode\u003E表达式后面那个表达式的值;\u003Ccode\u003Edone\u003C\u002Fcode\u003E属性是一个布尔值,表示是否遍历结束。\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 上述函数下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。\n\u002F\u002F 即每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,\n\u002F\u002F 直到遇到下一个yield表达式(或return语句)为止。\n\u002F\u002F 换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。\u003C\u002Fp\u003E\n\u003Cp\u003Ehw.next()\n\u002F\u002F { value: 'hello', done: false }\nhw.next()\n\u002F\u002F { value: 'world', done: false }\nhw.next()\n\u002F\u002F { value: 'ending', done: true }\nhw.next()\n\u002F\u002F { value: undefined, done: true }\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F*\n解释说明:\n上面代码一共调用了四次next方法。\n第一次调用,Generator 函数开始执行,直到遇到第一个yield表达式为止。\nnext方法返回一个对象,它的value属性就是当前yield表达式的值hello,\ndone属性的值false,表示遍历还没有结束。\u003C\u002Fp\u003E\n\u003Cp\u003E第二次调用,Generator 函数从上次yield表达式停下的地方,一直执行到下一个yield表达式。\nnext方法返回的对象的value属性就是当前yield表达式的值world,\ndone属性的值false,表示遍历还没有结束。\u003C\u002Fp\u003E\n\u003Cp\u003E第三次调用,Generator 函数从上次yield表达式停下的地方,一直执行到return语句\n(如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性,\n就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),\ndone属性的值true,表示遍历已经结束。\u003C\u002Fp\u003E\n\u003Cp\u003E第四次调用,此时 Generator 函数已经运行完毕,\nnext方法返回对象的value属性为undefined,done属性为true。\n以后再调用next方法,返回的都是这个值。\n*\u002F\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\n\u003Ch3 id=\"yield-表达式说明\"\u003Eyield 表达式说明\u003C\u002Fh3\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cp\u003EGenerator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003E遍历器对象的next方法的运行逻辑如下。\u003C\u002Fp\u003E\n\u003Cul\u003E\n\u003Cli\u003E遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。\u003C\u002Fli\u003E\n\u003Cli\u003E下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。\u003C\u002Fli\u003E\n\u003Cli\u003E如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。\u003C\u002Fli\u003E\n\u003Cli\u003E如果该函数没有return语句,则返回的对象的 value 属性值为 undefined。\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003E需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。\u003C\u002Fp\u003E\n\u003Cp\u003Efunction* gen(){\n yield 123 + 456;\n}\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F yield后面的表达式123 + 456,不会立即求值,只会在next方法将指针移到这一句时,才会求值.\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\n\u003Ch3 id=\"yield-表达式语法\"\u003Eyield 表达式语法\u003C\u002Fh3\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cp\u003Eyield表达式只能用在 Generator 函数里面,用在其他地方都会报错\u003C\u002Fp\u003E\n\u003Cp\u003E(function (){\n yield 1;\n})()\n\u002F\u002F SyntaxError: Unexpected number\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003Eyield表达式如果用在另一个表达式之中,必须放在圆括号里面\u003C\u002Fp\u003E\n\u003Cp\u003Efunction* demo() {\n console.log('Hello' + yield); \u002F\u002F SyntaxError\n console.log('Hello' + yield 123); \u002F\u002F SyntaxError\u003C\u002Fp\u003E\n\u003Cp\u003E console.log('Hello' + (yield)); \u002F\u002F OK\n console.log('Hello' + (yield 123)); \u002F\u002F OK\n}\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003Eyield表达式用作函数参数或放在赋值表达式的右边,可以不加括号\u003C\u002Fp\u003E\n\u003Cp\u003Efunction* demo() {\n foo(yield 'a', yield 'b'); \u002F\u002F OK\n let input = yield; \u002F\u002F OK\n}\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\n\u003Ch3 id=\"用法实例讲解\"\u003E用法实例讲解\u003C\u002Fh3\u003E\n\u003Cpre\u003E\u003Ccode\u003Evar arr = [1, [[2, 3], 4], [5, 6]];\n\nvar flat = function* (a) {\n a.forEach(function (item) {\n if (typeof item !== 'number') {\n yield* flat(item);\n } else {\n yield item;\n }\n });\n}; \u002F\u002F 错误用法\n\u002F\u002F forEach方法的参数是一个普通函数,但是在里面使用了yield表达式\n\n\u002F\u002F 一种修改方法是改用for循环。\nvar flat = function* (a) {\n var length = a.length;\n for (var i = 0; i < length; i++) {\n var item = a[i];\n if (typeof item !== 'number') {\n yield* flat(item);\n } else {\n yield item;\n }\n }\n};\n\nfor (var f of flat(arr)) {\n console.log(f);\n}\n\u002F\u002F 1, 2, 3, 4, 5, 6\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\n\u003Ch3 id=\"yield表达式与return语句\"\u003Eyield表达式与return语句\u003C\u002Fh3\u003E\n\u003Cul\u003E\n\u003Cli\u003Eyield 表达式与return 语句既有相似之处,也有区别。\u003C\u002Fli\u003E\n\u003Cli\u003E相似之处在于,都能返回紧跟在语句后面的那个表达式的值。\u003C\u002Fli\u003E\n\u003Cli\u003E区别在于每次遇到\u003Ccode\u003Eyield\u003C\u002Fcode\u003E,函数暂停执行,下一次再从该位置继续向后执行,而\u003Ccode\u003Ereturn\u003C\u002Fcode\u003E语句不具备位置记忆的功能。\u003C\u002Fli\u003E\n\u003Cli\u003E一个函数里面,\u003Cstrong\u003E只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield表达式\u003C\u002Fstrong\u003E。\u003C\u002Fli\u003E\n\u003Cli\u003E正常函数只能返回一个值,因为只能执行一次return;\u003C\u002Fli\u003E\n\u003Cli\u003EGenerator 函数可以返回一系列的值,因为可以有任意多个\u003Ccode\u003Eyield\u003C\u002Fcode\u003E。\u003C\u002Fli\u003E\n\u003Cli\u003E从另一个角度看,也可以说 Generator 生成了一系列的值。\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\n\u003Ch3 id=\"next-方法的参数\"\u003Enext 方法的参数\u003C\u002Fh3\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cp\u003E\u003Ccode\u003Eyield\u003C\u002Fcode\u003E表达式本身没有返回值,或者说总是返回\u003Ccode\u003Eundefined\u003C\u002Fcode\u003E。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。\u003C\u002Fp\u003E\n\u003Cp\u003Efunction* f() {\n for(var i = 0; true; i++) {\nvar reset = yield i;\nif(reset) { i = -1; }\n }\n}\nvar g = f();\u003C\u002Fp\u003E\n\u003Cp\u003Eg.next() \u002F\u002F { value: 0, done: false }\ng.next() \u002F\u002F { value: 1, done: false }\ng.next(true) \u002F\u002F { value: 0, done: false }\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F*\n上面代码先定义了一个可以无限运行的 Generator 函数f,\n如果next方法没有参数,每次运行到yield表达式,变量reset的值总是undefined。\u003C\u002Fp\u003E\n\u003Cp\u003E当next方法带一个参数true时,变量reset就被重置为这个参数(即true),因此i会等于-1,\n下一轮循环就会从-1开始递增。\n *\u002F\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003E这个功能有很重要的语法意义。Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003E通过next方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值。也就是说,可以在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。\u003C\u002Fp\u003E\n\u003Cp\u003Efunction* foo(x) {\n var y = 2 * (yield (x + 1));\n var z = yield (y \u002F 3);\n return (x + y + z);\n}\u003C\u002Fp\u003E\n\u003Cp\u003Evar a = foo(5);\na.next() \u002F\u002F Object{value:6, done:false}\na.next() \u002F\u002F Object{value:NaN, done:false}\na.next() \u002F\u002F Object{value:NaN, done:true}\u003C\u002Fp\u003E\n\u003Cp\u003Evar b = foo(5);\nb.next() \u002F\u002F { value:6, done:false }\nb.next(12) \u002F\u002F { value:8, done:false }\nb.next(13) \u002F\u002F { value:42, done:true }\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F*\n上面代码中,第二次运行next方法的时候不带参数,导致 y 的值等于2 * undefined(即NaN),\n除以 3 以后还是NaN,因此返回对象的value属性也等于NaN。\n第三次运行Next方法的时候不带参数,所以z等于undefined,\n返回对象的value属性等于5 + NaN + undefined,即NaN。\u003C\u002Fp\u003E\n\u003Cp\u003E如果向next方法提供参数,返回结果就完全不一样了。\n上面代码第一次调用b的next方法时,返回x+1的值6;\n第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y \u002F 3的值8;\n第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,\n这时x等于5,y等于24,所以return语句的值等于42。\n*\u002F\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003E注意,由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\n\u003Ch3 id=\"forof-循环\"\u003Efor...of 循环\u003C\u002Fh3\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Col\u003E\n\u003Cli\u003Efor...of循环可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法。\u003C\u002Fli\u003E\n\u003C\u002Fol\u003E\n\u003Cp\u003Efunction* foo() {\n yield 1;\n yield 2;\n yield 3;\n yield 4;\n yield 5;\n return 6;\n}\nfor (let v of foo()) {\n console.log(v);\n}\n\u002F\u002F 1 2 3 4 5\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 上面代码使用for...of循环,依次显示 5 个yield表达式的值。\n\u002F\u002F 这里需要注意,一旦next方法的返回对象的done属性为true,\n\u002F\u002F for...of循环就会中止,且不包含该返回对象,\n\u002F\u002F 所以上面代码的return语句返回的6,不包括在for...of循环之中。\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003E2)利用\u003Ccode\u003Efor...of\u003C\u002Fcode\u003E循环,可以写出遍历任意对象(object)的方法。原生的 JavaScript 对象没有遍历接口,无法使用for...of循环,通过 Generator 函数为它加上这个接口,就可以用了。\u003C\u002Fp\u003E\n\u003Cp\u003Efunction* objectEntries(obj) {\n let propKeys = Reflect.ownKeys(obj);\u003C\u002Fp\u003E\n\u003Cp\u003E for (let propKey of propKeys) {\nyield [propKey, obj[propKey]];\n }\n}\u003C\u002Fp\u003E\n\u003Cp\u003Elet jane = { first: 'Jane', last: 'Doe' };\u003C\u002Fp\u003E\n\u003Cp\u003Efor (let [key, value] of objectEntries(jane)) {\n console.log(\u003Ccode\u003E${key}: ${value}\u003C\u002Fcode\u003E);\n}\n\u002F\u002F first: Jane\n\u002F\u002F last: Doe\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003E3)加上遍历器接口的另一种写法是,将 Generator 函数加到对象的\u003Ccode\u003ESymbol.iterator\u003C\u002Fcode\u003E属性上面\u003C\u002Fp\u003E\n\u003Cp\u003Efunction* objectEntries() {\n let propKeys = Object.keys(this);\u003C\u002Fp\u003E\n\u003Cp\u003E for (let propKey of propKeys) {\nyield [propKey, this[propKey]];\n }\n}\u003C\u002Fp\u003E\n\u003Cp\u003Elet jane = { first: 'Jane', last: 'Doe' };\u003C\u002Fp\u003E\n\u003Cp\u003Ejane[Symbol.iterator] = objectEntries;\u003C\u002Fp\u003E\n\u003Cp\u003Efor (let [key, value] of jane) {\n console.log(\u003Ccode\u003E${key}: ${value}\u003C\u002Fcode\u003E);\n}\n\u002F\u002F first: Jane\n\u002F\u002F last: Doe\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003E4)除了for...of循环以外,\u003Ccode\u003E扩展运算符(...)\u003C\u002Fcode\u003E、\u003Ccode\u003E解构赋值\u003C\u002Fcode\u003E和\u003Ccode\u003EArray.from\u003C\u002Fcode\u003E方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。\u003C\u002Fp\u003E\n\u003Cp\u003Efunction* numbers () {\n yield 1\n yield 2\n return 3\n yield 4\n}\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 扩展运算符\n[...numbers()] \u002F\u002F [1, 2]\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F Array.from 方法\nArray.from(numbers()) \u002F\u002F [1, 2]\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 解构赋值\nlet [x, y] = numbers();\nx \u002F\u002F 1\ny \u002F\u002F 2\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F for...of 循环\nfor (let n of numbers()) {\n console.log(n)\n}\n\u002F\u002F 1\n\u002F\u002F 2\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\n\u003Ch3 id=\"generator函数的异步操作\"\u003EGenerator函数的异步操作\u003C\u002Fh3\u003E\n\u003Cpre\u003E\u003Ccode\u003Efunction* asyncJob() {\n \u002F\u002F ...其他代码\n var f = yield readFile(fileA);\n \u002F\u002F ...其他代码\n}\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\n\u003Cp\u003E函数asyncJob是一个协程,它的奥妙就在其中的yield命令。它表示执行到此处,执行权将交给其他协程。 也就是说,yield命令是异步两个阶段的分界线。 协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作,如果去除yield命令,简直一模一样。\u003C\u002Fp\u003E\n\u003Cp\u003EGenerator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。\u003Cbr\u003E整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用yield语句注明。\u003C\u002Fp\u003E\n\u003Ch3 id=\"异步任务的封装\"\u003E异步任务的封装\u003C\u002Fh3\u003E\n\u003Cpre\u003E\u003Ccode\u003Evar fetch = require('node-fetch');\n\nfunction* gen(){\n var url = 'https:\u002F\u002Fapi.github.com\u002Fusers\u002Fgithub';\n var result = yield fetch(url);\n console.log(result.bio);\n}\n\n\u002F\u002F Generator 函数封装了一个异步操作,该操作先读取一个远程接口,然后从 JSON 格式的数据解析信息。\n\u002F\u002F 就像前面说过的,这段代码非常像同步操作,除了加上了yield命令。\n\n\u002F\u002F 执行上述代码\nvar g = gen();\nvar result = g.next();\n\nresult.value.then(function(data){\n return data.json();\n}).then(function(data){\n g.next(data);\n});\n\n\u002F\u002F 首先执行 Generator 函数,获取遍历器对象,然后使用next方法(第二行),执行异步任务的第一阶段。\n\u002F\u002F 由于Fetch模块返回的是一个 Promise 对象,因此要用then方法调用下一个next方法。\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\n\u003Cp\u003E虽然 Generator 函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。\u003C\u002Fp\u003E\n\u003Ch2 id=\"async-函数\"\u003Easync 函数\u003C\u002Fh2\u003E\n\u003Cp\u003E学习指导:\u003Ca href=\"https:\u002F\u002Fwww.oschina.net\u002Faction\u002FGoToLink?url=http%3A%2F%2Fes6.ruanyifeng.com%2F%23docs%2Fasync\"\u003E阮一峰ES6 -- async函数\u003C\u002Fa\u003E\u003C\u002Fp\u003E\n\u003Ch3 id=\"含义\"\u003E含义\u003C\u002Fh3\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cp\u003Easync 函数是什么?一句话,它就是 Generator 函数的语法糖\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F generator 函数依次读取两个文件\nconst fs = require('fs');\nconst readFile = function (fileName) {\n return new Promise(function (resolve, reject) {\nfs.readFile(fileName, function(error, data) {\n if (error) return reject(error);\n resolve(data);\n});\n });\n};\nconst gen = function* () {\n const f1 = yield readFile('\u002Fetc\u002Ffstab');\n const f2 = yield readFile('\u002Fetc\u002Fshells');\n console.log(f1.toString());\n console.log(f2.toString());\n};\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F async函数,就是下面这样\nconst asyncReadFile = async function () {\n const f1 = await readFile('\u002Fetc\u002Ffstab');\n const f2 = await readFile('\u002Fetc\u002Fshells');\n console.log(f1.toString());\n console.log(f2.toString());\n};\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003Easync函数对 Generator 函数的改进,体现在以下四点:\u003C\u002Fp\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cstrong\u003E内置执行器\u003C\u002Fstrong\u003E —— Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cstrong\u003E更好的语义\u003C\u002Fstrong\u003E —— async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cstrong\u003E更广的适用性\u003C\u002Fstrong\u003E —— co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cstrong\u003E返回值是 Promise\u003C\u002Fstrong\u003E —— async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。\u003Cstrong\u003E进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。\u003C\u002Fstrong\u003E\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\n\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\n\u003Ch3 id=\"基本用法\"\u003E基本用法\u003C\u002Fh3\u003E\n\u003Cp\u003Easync函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。\u003C\u002Fp\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cp\u003E引入demo\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F* async 表示异步调用 返回一个Promise对象 \u003Cem\u003E\u002F\nasync function timeout(ms) {\nawait new Promise((resolve) => {\n setTimeout(resolve, ms);\n});\n} \u002F\u003C\u002Fem\u003E 与下边等价*\u002F\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F function timeout(ms) {\n\u002F\u002F return new Promise((resolve) => {\n\u002F\u002F setTimeout(resolve, ms);\n\u002F\u002F });\n\u002F\u002F }\u003C\u002Fp\u003E\n\u003Cp\u003Easync function asyncPrint(value, ms) {\nawait timeout(ms);\nconsole.log(value);\n}\u003C\u002Fp\u003E\n\u003Cp\u003EasyncPrint('hello world', 3000) \u002F\u002F 3000毫秒以后,输出hello world\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003Easync的表达方式\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 函数声明\nasync function foo() {}\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 函数表达式\nconst foo = async function () {};\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 对象的方法\nlet obj = { async foo() {} };\nobj.foo().then(...)\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F Class 的方法\nclass Storage {\n constructor() {\nthis.cachePromise = caches.open('avatars');\n }\u003C\u002Fp\u003E\n\u003Cp\u003E async getAvatar(name) {\nconst cache = await this.cachePromise;\nreturn cache.match(\u003Ccode\u003E\u002Favatars\u002F${name}.jpg\u003C\u002Fcode\u003E);\n }\n}\u003C\u002Fp\u003E\n\u003Cp\u003Econst storage = new Storage();\nstorage.getAvatar('jake').then(…);\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 箭头函数\nconst foo = async () => {};\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\n\u003Ch3 id=\"async-的语法\"\u003Easync 的语法\u003C\u002Fh3\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cp\u003E返回Promise 对象\u003C\u002Fp\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cp\u003Easync函数返回一个 Promise 对象。\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003Easync函数内部return语句返回的值,会成为then方法回调函数的参数。\u003C\u002Fp\u003E\n\u003Cp\u003Easync function f() {\n return 'hello world';\n}\u003C\u002Fp\u003E\n\u003Cp\u003Ef().then(v => console.log(v))\n\u002F\u002F "hello world"\n\u002F\u002F 函数 f 内部return命令返回的值,会被then方法回调函数接收到。\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003Easync函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。\u003C\u002Fp\u003E\n\u003Cp\u003Easync function f() {\n throw new Error('出错了');\n}\u003C\u002Fp\u003E\n\u003Cp\u003Ef().then(\n v => console.log(v),\n e => console.log(e)\n)\n\u002F\u002F Error: 出错了\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003EPromise 对象的状态变化\u003C\u002Fp\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cp\u003Easync函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003E也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。\u003C\u002Fp\u003E\n\u003Cp\u003Easync function getTitle(url) {\n let response = await fetch(url);\n let html = await response.text();\n return html.match(\u002F\u003Ctitle\u003E([\\s\\S]+)<\u002Ftitle>\u002Fi)[1];\n}\ngetTitle('\u003Ca href=\"https:\u002F\u002Ftc39.github.io\u002Fecma262\u002F').then(console.log)\"\u003Ehttps:\u002F\u002Ftc39.github.io\u002Fecma262\u002F').then(console.log)\u003C\u002Fa\u003E\n\u002F\u002F "ECMAScript 2017 Language Specification"\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 函数getTitle内部有三个操作:抓取网页、取出文本、匹配页面标题。\n\u002F\u002F 只有这三个操作全部完成,才会执行then方法里面的 console.log。\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003Eawait 命令\u003C\u002Fp\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cp\u003E正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。\u003C\u002Fp\u003E\n\u003Cp\u003Easync function f() {\n return await 123;\n}\u003C\u002Fp\u003E\n\u003Cp\u003Ef().then(v => console.log(v))\n\u002F\u002F 123\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F await命令的参数是数值123,它被转成 Promise 对象,并立即resolve\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003Eawait命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。\u003C\u002Fp\u003E\n\u003Cp\u003Easync function f() {\n await Promise.reject('出错了');\n}\u003C\u002Fp\u003E\n\u003Cp\u003Ef()\n.then(v => console.log(v))\n.catch(e => console.log(e))\n\u002F\u002F 出错了\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F await 语句前面没有 return,但是 reject 方法的参数依然传入了 catch 方法的回调函数。\n\u002F\u002F 这里如果在await前面加上return,效果是一样的。\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003E只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行\u003C\u002Fp\u003E\n\u003Cp\u003Easync function f() {\n await Promise.reject('出错了');\n await Promise.resolve('hello world'); \u002F\u002F 不会执行\n}\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003E可以将第一个await放在\u003Ccode\u003Etry...catch\u003C\u002Fcode\u003E结构里面,这样不管这个异步操作是否成功,第二个await都会执行。\u003C\u002Fp\u003E\n\u003Cp\u003Easync function f() {\n try {\nawait Promise.reject('出错了');\n } catch(e) {\n }\n return await Promise.resolve('hello world');\n}\u003C\u002Fp\u003E\n\u003Cp\u003Ef()\n.then(v => console.log(v))\n\u002F\u002F hello world\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003E另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误\u003C\u002Fp\u003E\n\u003Cp\u003Easync function f() {\n await Promise.reject('出错了')\n.catch(e => console.log(e));\n return await Promise.resolve('hello world');\n}\u003C\u002Fp\u003E\n\u003Cp\u003Ef()\n.then(v => console.log(v))\n\u002F\u002F 出错了\n\u002F\u002F hello world\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\n\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\n\u003Ch3 id=\"使用注意点\"\u003E使用注意点\u003C\u002Fh3\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cp\u003E第一点,前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在\u003Ccode\u003Etry...catch\u003C\u002Fcode\u003E代码块中。\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003E第二点,多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。\u003C\u002Fp\u003E\n\u003Cp\u003Elet foo = await getFoo();\nlet bar = await getBar();\n\u002F\u002F 上面代码中,getFoo和getBar是两个独立的异步操作(即互不依赖),被写成继发关系。\n\u002F\u002F 这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 写法一\nlet [foo, bar] = await Promise.all([getFoo(), getBar()]);\n\u002F\u002F 写法二\nlet fooPromise = getFoo();\nlet barPromise = getBar();\nlet foo = await fooPromise;\nlet bar = await barPromise;\n\u002F\u002F getFoo和getBar都是同时触发,这样就会缩短程序的执行时间\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003E第三点,await命令只能用在async函数之中,如果用在普通函数,就会报错。\u003C\u002Fp\u003E\n\u003Cp\u003Easync function dbFuc(db) {\n let docs = [{}, {}, {}];\u003C\u002Fp\u003E\n\u003Cp\u003E \u002F\u002F 报错\n docs.forEach(function (doc) {\nawait db.post(doc);\n });\n}\n\u002F\u002F 上面代码会报错,因为await用在普通函数之中了\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 采用for循环\nasync function dbFuc(db) {\n let docs = [{}, {}, {}];\u003C\u002Fp\u003E\n\u003Cp\u003E for (let doc of docs) {\nawait db.post(doc);\n }\n}\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 如果确实希望多个请求并发执行,可以使用Promise.all方法。\n\u002F\u002F 当三个请求都会resolved时,下面两种写法效果相同。\nasync function dbFuc(db) {\n let docs = [{}, {}, {}];\n let promises = docs.map((doc) => db.post(doc));\u003C\u002Fp\u003E\n\u003Cp\u003E let results = await Promise.all(promises);\n console.log(results);\n}\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 或者使用下面的写法\nasync function dbFuc(db) {\n let docs = [{}, {}, {}];\n let promises = docs.map((doc) => db.post(doc));\u003C\u002Fp\u003E\n\u003Cp\u003E let results = [];\n for (let promise of promises) {\nresults.push(await promise);\n }\n console.log(results);\n}\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\n\u003Ch3 id=\"async函数实现原理\"\u003Easync函数实现原理\u003C\u002Fh3\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cp\u003Easync 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里\u003C\u002Fp\u003E\n\u003Cp\u003Easync function fn(args) {\n \u002F\u002F ...\n}\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 等同于\nfunction fn(args) {\n return spawn(function* () {\n\u002F\u002F ...\n });\n}\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 所有的async函数都可以写成上面的第二种形式,其中的spawn函数就是自动执行器。\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\n\u003Ch3 id=\"场景再现\"\u003E场景再现\u003C\u002Fh3\u003E\n\u003Cul\u003E\n\u003Cli\u003E\u003Cp\u003E假定某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值。\u003C\u002Fp\u003E\n\u003Cp\u003Easync function chainAnimationsAsync(elem, animations) {\n let ret = null; \u002F\u002F 变量ret用来保存上一个动画的返回值\n try {\nfor(let anim of animations) {\n ret = await anim(elem);\n}\n } catch(e) {\n\u002F* 忽略错误,继续执行 *\u002F\n }\n return ret;\n}\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003Cli\u003E\u003Cp\u003E依次远程读取一组 URL,然后按照读取的顺序输出结果。\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 远程操作继发.只有前一个 URL 返回结果,才会去读取下一个 URL,这样做效率很差,非常浪费时间\nasync function logInOrder(urls) {\n for (const url of urls) {\nconst response = await fetch(url);\nconsole.log(await response.text());\n }\n}\u003C\u002Fp\u003E\n\u003Cp\u003E\u002F\u002F 并发发出远程请求\nasync function logInOrder(urls) {\n \u002F\u002F 并发读取远程URL\n const textPromises = urls.map(async url => {\nconst response = await fetch(url);\nreturn response.text();\n });\u003C\u002Fp\u003E\n\u003Cp\u003E \u002F\u002F 按次序输出\n for (const textPromise of textPromises) {\nconsole.log(await textPromise);\n }\n}\u003C\u002Fp\u003E\n\u003C\u002Fli\u003E\n\u003C\u002Ful\u003E\n",tags:"[{\"id\":12041,\"uuid\":\"97567615\",\"name\":\"async\",\"icon\":\"\",\"status\":1,\"createTime\":\"2022-09-23T22:52:22.330109+08:00\",\"updateTime\":\"2022-09-23T22:52:22.330109+08:00\"},{\"id\":12612,\"uuid\":\"63234883\",\"name\":\"promise\",\"icon\":\"\",\"status\":1,\"createTime\":\"2022-09-23T23:10:27.591066+08:00\",\"updateTime\":\"2022-09-23T23:10:27.591066+08:00\"},{\"id\":11074,\"uuid\":\"74326380\",\"name\":\"函数调用\",\"icon\":\"\",\"status\":1,\"createTime\":\"2022-09-23T22:35:05.521477+08:00\",\"updateTime\":\"2022-09-23T22:35:05.521477+08:00\"},{\"id\":12040,\"uuid\":\"07295658\",\"name\":\"next\",\"icon\":\"\",\"status\":1,\"createTime\":\"2022-09-23T22:52:22.298984+08:00\",\"updateTime\":\"2022-09-23T22:52:22.298984+08:00\"},{\"id\":11700,\"uuid\":\"13903209\",\"name\":\"const\",\"icon\":\"\",\"status\":1,\"createTime\":\"2022-09-23T22:46:48.391161+08:00\",\"updateTime\":\"2022-09-23T22:46:48.391161+08:00\"},{\"id\":14009,\"uuid\":\"88939860\",\"name\":\"yield\",\"icon\":\"\",\"status\":1,\"createTime\":\"2022-09-24T00:14:48.252124+08:00\",\"updateTime\":\"2022-09-24T00:14:48.252124+08:00\"},{\"id\":11120,\"uuid\":\"51380297\",\"name\":\"前端\",\"icon\":\"\",\"status\":1,\"createTime\":\"2022-09-23T22:35:22.489653+08:00\",\"updateTime\":\"2022-09-23T22:35:22.489653+08:00\"},{\"id\":11120,\"uuid\":\"51380297\",\"name\":\"前端\",\"icon\":\"\",\"status\":1,\"createTime\":\"2022-09-23T22:35:22.489653+08:00\",\"updateTime\":\"2022-09-23T22:35:22.489653+08:00\"}]",homeImg:b,createTime:"2021-10-11T19:39:43+08:00",updateTime:"1970-01-01T08:00:00+08:00",publishTime:"2021-10-11T19:39:44+08:00",readCount:946,favoriteCount:a,zanCount:a,isAuthorBlog:c},otherBlogList:[{uuid:"6917592658",title:"OpenVPN下载、安装、配置及使用详解 "},{uuid:"0907804122",title:"GitHub神器,一个可以白嫖全网无损音乐的神器 "},{uuid:"9665141123",title:"OpenWrt 路由器过滤广告的N种方法 "},{uuid:"0868920465",title:"Python—执行系统命令的四种方法(os.system、os.popen、commands、subprocess) "},{uuid:"8340561847",title:"SS端加密以及obfs混淆 "}],recommendBlogList:[{uuid:"2802547579",title:"MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1",title2:b,intro:"文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\\mode:sql\\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s",createTime:d,updateTime:d,publishTime:"2021-08-20T13:55:40+08:00",homeImg:"https:\u002F\u002Fimg-hello-world.oss-cn-beijing.aliyuncs.com\u002Fed6c6174f1775f65a3fbe98488391419.png",readCount:4252,zanCount:a,favoriteCount:a,status:a,commentCount:a,profile:u,nicker:u,avatar:"https:\u002F\u002Fimg-hello-world.oss-cn-beijing.aliyuncs.com\u002Fimgs\u002Fcd34cdaab69ede1e2bf28de235151c34.webp",collectionId:a,recommendStatus:a,userStatus:a,auditReason:b},{uuid:"0507154872",title:"swap空间的增减方法 ",title2:b,intro:"(1)增大swap空间去激活swap交换区:swapoff v \u002Fdev\u002Fvg00\u002Flvswap扩展交换lv:lvextend L 10G \u002Fdev\u002Fvg00\u002Flvswap重新生成swap交换区:mkswap \u002Fdev\u002Fvg00\u002Flvswap激活新生成的交换区:swapon v \u002Fdev\u002Fvg00\u002Flvswap",createTime:d,updateTime:d,publishTime:"2021-10-12T09:53:53+08:00",homeImg:b,readCount:1304,zanCount:a,favoriteCount:a,status:a,commentCount:a,profile:v,nicker:v,avatar:"https:\u002F\u002Fimg-hello-world.oss-cn-beijing.aliyuncs.com\u002Fimgs\u002F3149e13a96d0d27780fd76b05b6ea101.jfif",collectionId:a,recommendStatus:a,userStatus:a,auditReason:b},{uuid:"9382788506",title:"皕杰报表之UUID",title2:b,intro:"​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为",createTime:d,updateTime:d,publishTime:"2022-07-08T08:32:23+08:00",homeImg:b,readCount:1214,zanCount:a,favoriteCount:a,status:a,commentCount:a,profile:"34035044",nicker:"helloworld_34035044",avatar:"https:\u002F\u002Fimg-hello-world.oss-cn-beijing.aliyuncs.com\u002Fimgs\u002F0d250ec5492da88592013bbd302402ae.jfif",collectionId:a,recommendStatus:a,userStatus:a,auditReason:b},{uuid:"1863625810",title:"手写Java HashMap源码",title2:b,intro:"HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22",createTime:d,updateTime:d,publishTime:"2024-07-08T22:15:50.892629+08:00",homeImg:b,readCount:1137,zanCount:a,favoriteCount:a,status:a,commentCount:a,profile:"waitrabbit",nicker:"待兔",avatar:"https:\u002F\u002Fimg-hello-world.oss-cn-beijing.aliyuncs.com\u002Fimgs\u002F4f77da7232573922a580077ad6e6e085.jpg",collectionId:a,recommendStatus:a,userStatus:a,auditReason:b},{uuid:"3468005401",title:"2020年前端实用代码段,为你的工作保驾护航",title2:b,intro:"有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: \"OK\",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )",createTime:d,updateTime:d,publishTime:"2021-06-03T22:46:22+08:00",homeImg:"https:\u002F\u002Fimg-hello-world.oss-cn-beijing.aliyuncs.com\u002Fe7130dfd1edf85ccee4a9e73aa0b77dd.jpeg",readCount:2448,zanCount:a,favoriteCount:a,status:a,commentCount:a,profile:w,nicker:w,avatar:"https:\u002F\u002Fimg-hello-world.oss-cn-beijing.aliyuncs.com\u002Fimgs\u002Fd4566ad9edfb956dba92fc21f5e1561f.webp",collectionId:a,recommendStatus:a,userStatus:a,auditReason:b},{uuid:"6029686498",title:"mysql设置时区 ",title2:b,intro:"mysql设置时区mysql\\_query(\"SETtime\\_zone'8:00'\")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\\_id)asdevice,CONVERT\\_TZ(FROM\\_UNIXTIME(reg\\_time),'08:00','0",createTime:d,updateTime:d,publishTime:"2021-10-11T12:01:24+08:00",homeImg:b,readCount:1382,zanCount:a,favoriteCount:a,status:a,commentCount:a,profile:f,nicker:f,avatar:h,collectionId:a,recommendStatus:a,userStatus:a,auditReason:b},{uuid:"1930020008",title:"HIVE 时间操作函数 ",title2:b,intro:"日期函数UNIX时间戳转日期函数: from\\_unixtime语法:   from\\_unixtime(bigint unixtime\\, string format\\)返回值: string说明: 转化UNIX时间戳(从19700101 00:00:00 UTC到指定时间的秒数)到当前时区的时间格式举例:hive   selec",createTime:d,updateTime:d,publishTime:"2021-10-11T18:34:29+08:00",homeImg:b,readCount:962,zanCount:a,favoriteCount:a,status:a,commentCount:a,profile:g,nicker:g,avatar:i,collectionId:a,recommendStatus:a,userStatus:a,auditReason:b},{uuid:"4384340365",title:"00:Java简单了解 ",title2:b,intro:"浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。",createTime:d,updateTime:d,publishTime:"2021-10-11T08:43:43+08:00",homeImg:b,readCount:1316,zanCount:a,favoriteCount:a,status:a,commentCount:a,profile:f,nicker:f,avatar:h,collectionId:a,recommendStatus:a,userStatus:a,auditReason:b},{uuid:"3395108718",title:"Django中Admin中的一些参数配置 ",title2:b,intro:"设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable",createTime:d,updateTime:d,publishTime:"2021-10-11T16:54:04+08:00",homeImg:b,readCount:1237,zanCount:a,favoriteCount:a,status:a,commentCount:a,profile:g,nicker:g,avatar:i,collectionId:a,recommendStatus:a,userStatus:a,auditReason:b},{uuid:"2102334911",title:"MySQL部分从库上面因为大量的临时表tmp_table造成慢查询 ",title2:b,intro:"背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_",createTime:d,updateTime:d,publishTime:"2021-10-11T11:24:10+08:00",homeImg:b,readCount:1588,zanCount:a,favoriteCount:a,status:a,commentCount:a,profile:f,nicker:f,avatar:h,collectionId:a,recommendStatus:a,userStatus:a,auditReason:b},{uuid:"7920817787",title:"Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除",title2:b,intro:"大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这",createTime:d,updateTime:d,publishTime:"2024-01-18T17:34:27.824977+08:00",homeImg:"https:\u002F\u002Fimg-hello-world.oss-cn-beijing.aliyuncs.com\u002Fimgs\u002Faeaded5a375b9d90ad54a2731b017114.png",readCount:559,zanCount:a,favoriteCount:a,status:a,commentCount:a,profile:"pdcfighting",nicker:"Python进阶者",avatar:"https:\u002F\u002Fimg-hello-world.oss-cn-beijing.aliyuncs.com\u002Fimgs\u002Fff45645a048833b06e967323e7411c91.webp",collectionId:a,recommendStatus:a,userStatus:a,auditReason:b}],userInfo:{profile:g,avatar:i,nicker:g,level:a,job:b,company:b,sex:a,slogan:"接天莲叶无穷碧,映日荷花别样红。",blogCount:16974,fansCount:42,zanCount:28,followed:c,isZaned:c,isFavorited:c,webEnable:a,website:b,websiteDomain:b,wechatQrcode:b,wechatOfficialAccount:b}},currentCateId:b,recommendSpecialList:[],userAndBlogActionInfo:{},isLoading:c,isFinished:c,searchList:[],isLoadingSearch:c,isFinishedSearch:c,recommendLessonList:[],recommendLessonListByBlog:[{id:65,uuid:"7011629094",userId:x,name:"Andriod设计模式实战",subtitle:"结合Andriod知识点讲解设计模式实战",content:b,html:b,price:a,discountAmount:a,status:y,reason:b,level:z,type:A,mark:a,canRefund:a,cover:"https:\u002F\u002Fimg-hello-world.oss-cn-beijing.aliyuncs.com\u002Fimgs\u002F80fd22993d90e1283227f808922efd96.png",payCount:37,updatedAt:"2023-08-29T00:40:06.110205+08:00",createdAt:"2023-08-28T21:22:26.082877+08:00",deletedAt:e},{id:74,uuid:"4889453415",userId:x,name:B,subtitle:B,content:b,html:b,price:a,discountAmount:a,status:y,reason:b,level:z,type:A,mark:a,canRefund:a,cover:"https:\u002F\u002Fimg-hello-world.oss-cn-beijing.aliyuncs.com\u002Fimgs\u002Fc4b7688afeb6ec0a56c562034d1ed6e2.png",payCount:19,updatedAt:"2023-09-08T09:27:39.302726+08:00",createdAt:"2023-09-07T09:25:08.415001+08:00",deletedAt:e}]},special:{mostSpecialCountUserList:[],specialList:[],specialCateList:[],bannerList:[],specialDetail:{},specialDetailList:{},chapterList:[],specialListByCate:[],mySpecialDetail:e,isLoading:c,isFinished:c},tutorial:{bigCateList:[],tutorialData:[],tutorialDetail:{},chapterList:[],tutorialOverview:{}}},serverRendered:t,routePath:"\u002Fp\u002F2962064446",config:{_app:{basePath:"\u002F",assetsPath:"\u002F_nuxt\u002F",cdnURL:e}}}}(0,"",false,"0001-01-01T00:00:00Z",null,"Wesley13","Stella981","https:\u002F\u002Fimg-hello-world.oss-cn-beijing.aliyuncs.com\u002Fimgs\u002F46847d754406b0102dee7a1f54d14f92.jfif","https:\u002F\u002Fimg-hello-world.oss-cn-beijing.aliyuncs.com\u002Fimgs\u002F1bad3e5246214111b0d7a482fc5beec5.jfif","\u002Fmanage\u002FgetCollectionList","\u002Fmanage\u002FgetMyBlogDetail","\u002Fspecial\u002FupdateSection","\u002Fmanage\u002FgetMySectionDetail","\u002Fspecial\u002FsubscribeSpecial","\u002Fspecial\u002FunSubscribeSpecial","\u002Fspecial\u002FmodifySpecial","\u002Ftutorial\u002FgetTutorialList","\u002Ftutorial\u002FgetTutorialDetail","\u002Faccess\u002FmodifyPassword",true,"blmius","Easter79","Jacquelyn38",7689,30,3,1,"Android进阶之音视频技术"));</script><script src="/_nuxt/hw.165.js?t=1727528274054" defer></script><script src="/_nuxt/hw.116.js?t=1727528274054" defer></script><script src="/_nuxt/hw.0.js?t=1727528274054" defer></script><script src="/_nuxt/hw.1.js?t=1727528274054" defer></script><script src="/_nuxt/hw.2.js?t=1727528274054" defer></script><script src="/_nuxt/hw.166.js?t=1727528274054" defer></script><script src="/_nuxt/hw.14.js?t=1727528274054" defer></script><script src="/_nuxt/hw.171.js?t=1727528274054" defer></script><script src="/_nuxt/hw.15.js?t=1727528274054" defer></script> </body> </html>