ES6读书笔记(三)

ES6读书笔记(三)

前言

前段时间整理了ES6的读书笔记:《ES6读书笔记(一)》《ES6读书笔记(二)》,现在为第三篇,本篇内容包括:

  • 一、Promise
  • 二、Iterator和for of循环
  • 三、Generator
  • 四、async

本文笔记也主要是根据阮一峰老师的《ECMAScript 6 入门》和平时的理解进行整理的,希望对你有所帮助,喜欢的就点个赞吧!

一、Promise

1. 执行顺序

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// resolved
复制代码

2.异步加载图片:

function loadImageAsync(url) {
  return new Promise(function(resolve, reject) {
    const image = new Image();

    image.onload = function() {
      resolve(image);
    };

    image.onerror = function() {
      reject(new Error('Could not load image at ' + url));
    };

    image.src = url;
  });
}
复制代码

3.用Promise对象实现Ajax:

const getJSON = function(url) {
  const promise = new Promise(function(resolve, reject){
    const handler = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();

  });

  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出错了', error);
});
复制代码

4.then方法返回的是一个新的Promise实例(不是原来那个Promise实例)

5.如果 Promise 状态已经变成resolved,再抛出错误是无效的:

const promise = new Promise(function(resolve, reject) {
  resolve('ok');
  throw new Error('test');
});
promise
  .then(function(value) { console.log(value) })
  .catch(function(error) { console.log(error) });
// ok
复制代码

上面代码中,Promise 在resolve语句后面,再抛出错误,不会被捕获,等于没有抛出。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了

6.跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应,通俗的说法就是“Promise 会吃掉错误”:

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    resolve(x + 2);   // 会报错,因为x没有声明
  });
};

someAsyncThing().then(function() {
  console.log('everything is great');
});

setTimeout(() => { console.log(123) }, 2000);  // 虽然以上有错误,但没有阻塞后面的代码
// Uncaught (in promise) ReferenceError: x is not defined
// 123
复制代码

7.

const promise = new Promise(function (resolve, reject) {
  resolve('ok');
  setTimeout(function () { throw new Error('test') }, 0)
});
promise.then(function (value) { console.log(value) });
// ok
// Uncaught Error: test
复制代码

上面代码中,Promise 指定在下一轮“事件循环”再抛出错误。到了那个时候,Promise 的运行已经结束了,所以这个错误是在 Promise 函数体外抛出的,会冒泡到最外层,成了未捕获的错误,相当于是js引擎去执行了这个回调,而不是在promise内部执行。

一般总是建议,Promise 对象后面要跟catch方法,这样可以处理 Promise 内部发生的错误。catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法:

  • ①如果有错误,但没有去catch,则会阻塞promise内部的代码,但不会阻塞外部的代码;
  • ②如果有catch,但是没有错误,则会跳过catch,继续执行后面的代码;
  • ③如果有catch,然后被catch捕获了错误,那依旧可以继续执行后面的代码;
  • ④如果有catch,catch捕获到了前面的错误,但catch内部又有错误的话,则会阻塞后面的代码,除非后面再链式调用catch捕获该错误。

以上总结就是只要promise内部有错误没有被捕获,就会阻塞内部代码,但不会阻塞外部代码。

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};

someAsyncThing()
.catch(function(error) {
  console.log('oh no', error);
})
.then(function() {
  console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
// carry on
//--------------------------------------------
const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};

someAsyncThing().then(function() {
  return someOtherAsyncThing();
}).catch(function(error) {
  console.log('oh no', error);
    y + 2;  // y 没有声明会报错,且这个错误未被捕获,会阻塞后面的代码
}).then(function() {
  console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
复制代码

8.Promise.prototype.finally()

  • ①finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作:
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
复制代码

上面代码中,不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。

  • ②finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

  • ③finally本质上是then方法的特例:

promise
.finally(() => {
  // 语句
});

// 等同于
promise
.then(
  result => {
    // 语句
    return result;
  },
  error => {
    // 语句
    throw error;
  }
);
复制代码
  • ④finally方法总是会返回原来的值:
// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})

// resolve 的值是 2
Promise.resolve(2).finally(() => {})

// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})

// reject 的值是 3
Promise.reject(3).finally(() => {})
复制代码

9.Promise.all(数组或具有 Iterator 接口,且返回的每个成员都是 Promise 实例)

  • ①如果参数全为fulfilled,则返回对应的数组结果(是等全部得到结果了再一起返回),但如果有一个是rejected,则返回第一个rejected的返回值,状态就为rejected。

  • ②catch后会变为resolved:

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
// reject(“world”);
})
.then(result => result)
.catch(e => e);        // catch后会变为resolved

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));   // 传入的p2有自己的catch,所以不会触发这里的catch,所以没有捕获到错误,所以就相当于都是执行正确的,所以会有结果
// ["hello", Error: 报错了]
复制代码

10.Promise.race

参数中谁率先改变了状态,就返回谁的状态,这意味着只返回一个结果

11.Promise.resolve()

  • ①Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。 所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve方法:
const p = Promise.resolve();

p.then(function () {
  // ...
});
复制代码
  • ②立即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。
setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

// one
// two
// three
复制代码

12.Promise.reject()

Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致:

const thenable = {
  then(resolve, reject) {
    reject('出错了');
  }
};
Promise.reject(thenable)
.catch(e => {
  console.log(e === thenable)
})
// true
复制代码

上面代码中,Promise.reject方法的参数是一个thenable对象,执行以后,后面catch方法的参数不是reject抛出的“出错了”这个字符串,而是thenable对象。

13.如果对于一个函数,不管是同步或异步,都想使用then方法指定下一流程,可使用以下方式,让它是同步时就按同步执行,是异步时就按异步执行:

不要直接使用promise.resolve(),因为如果是同步函数,会在本轮事件循环末尾才会执行:

const f = () => console.log('now');
Promise.resolve().then(f);  // then才是微任务,resolve时还是同步的
console.log('next');
// next
// now
复制代码
  • ①使用async:
const f = () => console.log('now');
(async () => f())()
.then(...)
.catch(...);

console.log('next');
// now
// next
复制代码
  • ②使用new Promise():
const f = () => console.log('now');
(
  () => new Promise(
    resolve => resolve(f())
  )
)();
console.log('next');
// now
// next
复制代码
  • ③一个提案,提供Promise.try方法替代上面的写法:浏览器目前会报错
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
复制代码

二、Iterator和for of循环

1. Iterator(遍历器)的概念

JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。

Iterator 接口是一种数据遍历的协议,只要调用遍历器对象的next方法,就会得到一个对象,表示当前遍历指针所在的那个位置的信息。

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for…of循环,Iterator 接口主要供for…of消费。

Iterator 的遍历过程是这样的:

  • (1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
  • (2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  • (3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
  • (4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。

一个模拟next方法返回值的例子:

var it = makeIterator(['a', 'b']);

it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {
   value: array[nextIndex++], done: false} :
        {
   value: undefined, done: true};
    }
  };
}
复制代码

2.ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。Symbol.iterator属性本身是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内。

const obj = {
  [Symbol.iterator] : function () {
    return {
      next: function () {
        return {
          value: 1,
          done: true
        };
      }
    };
  }
};
复制代码

3.原生具备 Iterator 接口的数据结构如下:不含对象

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

4.数组的Symbol.iterator属性:

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
复制代码

5.对象(Object)之所以没有默认部署 Iterator 接口,是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。本质上,遍历器是一种线性处理,对于任何非线性的数据结构,部署遍历器接口,就等于部署一种线性转换。不过,严格地说,对象部署遍历器接口并不是很必要,因为这时对象实际上被当作 Map 结构使用,ES5 没有 Map 结构,而 ES6 原生提供了。

6.有一些场合会默认调用 Iterator 接口(即Symbol.iterator方法):

  • ①解构赋值
  • ②扩展运算符:这样就可对有Iterator接口的数据结构使用扩展运算符转为数组,而对于没有Iterator接口的类数组,可采用Array.from转为数组,这样就具有了Iterator接口
  • ③yield*

yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口:

let generator = function* () {
  yield 1;
  yield* [2,3,4];
  yield 5;
};

var iterator = generator();

iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
复制代码
  • ④其他场合 由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口:

  • for…of

  • Array.from()

  • Map(), Set(), WeakMap(), WeakSet()(比如new Map([[‘a’,1],[‘b’,2]]))

  • Promise.all()

  • Promise.race()

7. Iterator接口与Generator函数

Symbol.iterator方法的最简单实现:

let myIterable = {
  [Symbol.iterator]: function* () {
    yield 1;
    yield 2;
    yield 3;
  }
}
[...myIterable] // [1, 2, 3]

// 或者采用下面的简洁写法

let obj = {
  * [Symbol.iterator]() {
    yield 'hello';
    yield 'world';
  }
};

for (let x of obj) {
  console.log(x);
}
// "hello"
// "world"
复制代码

8.for…of循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。这一点跟for…in循环也不一样。

let arr = [3, 5, 7];
arr.foo = 'hello';

for (let i in arr) {
  console.log(i); // "0", "1", "2", "foo" 这也说明了for in遍历了自身及原型上的可枚举属性
}

for (let i of arr) {
  console.log(i); // "3", "5", "7"
}
复制代码

9. Map遍历得到的是数组,Set遍历得到的是单个值:

let map = new Map().set('a', 1).set('b', 2);
Map;   // {"a" => 1, "b" => 2}
for (let pair of map) {
  console.log(pair);
}
// ['a', 1]
// ['b', 2]

for (let [key, value] of map) {
  console.log(key + ' : ' + value);
}
// a : 1
// b : 2
复制代码

10. 可用Array.from将不具有iterator接口的类数组对象转为数组,这样也就具有了iterator接口:

let arrayLike = { length: 2, 0: 'a', 1: 'b' };

// 报错
for (let x of arrayLike) {
  console.log(x);
}

// 正确
for (let x of Array.from(arrayLike)) {
  console.log(x);
}
复制代码

11.循环对比:

  • for in 会遍历原型可枚举属性,为遍历对象而生,尽管对象没有iterator接口
  • forEach不能中途跳出循环
  • for of 可中途跳出循环,不会遍历原型可枚举属性,针对数组

三、Generator

1. 执行Generator(生成器)返回一个遍历器对象,这个遍历器对象可以依次遍历Generator函数内部的每一个状态,yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”):

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

var hw = helloWorldGenerator();
hw.next()  // 遇到第一个yield,暂停,然后返回yield后面的表达式的值
// { value: 'hello', done: false }

hw.next()  // 从上次暂停的地方往下执行,遇到第二个yield后暂停,返回值
// { value: 'world', done: false }

hw.next()  // 从上次暂停的地方往下执行,发现没有yield了,所以一直往下执行,直到遇到// return,如果没有return则返回undefined
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }
复制代码

定义方式:

function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· }   // 推荐这种写法
function*foo(x, y) { ··· }
复制代码

2. 遍历器对象的next方法的运行逻辑如下:

  • (1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。

  • (2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。

  • (3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

  • (4)如果该函数没有return语句,则返回的对象的value属性值为undefined。

需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。

3. Generator 函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数。

function* f() {
  console.log('执行了!')
}

var generator = f();

setTimeout(function () {
  generator.next()
}, 2000);
复制代码

4. 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
}
复制代码

5.与 Iterator 接口的关系:

任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。

由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口:

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]
复制代码

上面代码中,Generator 函数赋值给Symbol.iterator属性,从而使得myIterable对象具有了 Iterator 接口,可以被…运算符遍历了。

Generator 函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身:

function* gen(){
  // some code
}

var g = gen();

g[Symbol.iterator]() === g
// true
复制代码

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

function* f() {
  for(var i = 0; true; i++) {
    var reset = yield i;  // yield i表达式是没有返回值的,或者说返回undefined
    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 } 相当于给reset赋值为true,重置了i的值
复制代码

next参数的值是传给上一个yield表达式的返回值,所以这也意味着第一个next的参数是无效的,所以不需要传,即第一个next是用于启动遍历器对象:

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} yield (x + 1)的返回值是undefined,所以乘2再除3得到的是NaN
a.next() // Object{value:NaN, done:true} 5 + NaN + undefined为NaN

var b = foo(5);
b.next() // { value:6, done:false } 5+1得到6
b.next(12) // { value:8, done:false } 12赋给yield (x + 1),然后乘2除3得到8,即(y / 3)的值
b.next(13) // { value:42, done:true } 同理

//-----------------------------------------
function* dataConsumer() {
  console.log('Started');
  console.log(`1. ${
     yield}`);
  console.log(`2. ${
     yield}`);
  return 'result';
}

let genObj = dataConsumer();
genObj.next();
// Started
// {value: undefined, done: false}
genObj.next('a')
// 1. a
// {value: undefined, done: false}
genObj.next('b')
// 2. b
// {value: "result", done: true}
复制代码

7. for of 循环:

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 没有6,因为一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象
复制代码

8. Generator.prototype.throw()

Generator 函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获:

var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log('内部捕获', e);
  }
};

var i = g();
i.next();  // 要捕获错误,必须先执行一次next来启动遍历器对象

try {
  i.throw('a');
  i.throw('b');
} catch (e) {
  console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b
复制代码

9. throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法:

var gen = function* gen(){
  try {
    yield console.log('a');
  } catch (e) {
    // ...
  }
  yield console.log('b');
  yield console.log('c');
}

var g = gen();
g.next() // a
g.throw() // b
g.next() // c
复制代码

10. Generator.prototype.return()

Generator 函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历 Generator 函数:

function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

var g = gen();

g.next()        // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next()        // { value: undefined, done: true }
复制代码

如果return方法调用时,不提供参数,则返回值的value属性为undefined:

function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

var g = gen();

g.next()        // { value: 1, done: false }
g.return() // { value: undefined, done: true }
复制代码

如果 Generator 函数内部有try…finally代码块,且正在执行try代码块,那么return方法会推迟到finally代码块执行完再执行。

function* numbers () {
  yield 1;
  try {
    yield 2;
    yield 3;
  } finally {
    yield 4;
    yield 5;
  }
  yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
复制代码

上面代码中,调用return方法后,就开始执行finally代码块,然后等到finally代码块执行完,再执行return方法。

11. 在一个 Generator 函数里面执行另一个 Generator 函数:

function* bar() {
  yield 'x';
  yield* foo();  // 加了*号就是返回遍历器内部值,相当于调用了*后面变量的iterator接口,否则返回遍历器对象
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  yield 'a';
  yield 'b';
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  for (let v of foo()) {
    yield v;
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// "x"
// "a"
// "b"
// "y"
复制代码
3.1 异步应用

1.异步简单说就是不连续的执行任务,类似一个协程的过程:

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

其中的yield命令。它表示执行到此处,执行权将交给其他协程。也就是说,yield命令是异步两个阶段的分界线,协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。

2.Generator 就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。

3.传值调用:先计算参数值再传入函数体内使用。 传名调用:直接将参数表达式传入函数体内,使用到时再进行求值。

4.Generator的异步应用中何时调用第一步,何时调用第二步,此时就需要使用thunk函数,相当于“传名调用”,编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体,这个临时函数就叫做 Thunk 函数:

function f(m) {
  return m * 2;
}

f(x + 5);

// 等同于

var thunk = function () {
  return x + 5;
};

function f(thunk) {
  return thunk() * 2;
}
复制代码

5.JavaScript 语言的 Thunk 函数

JavaScript 语言是传值调用,它的 Thunk 函数含义有所不同。在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成一个只接受回调函数作为参数的单参数函数,类似柯里化:

// 正常版本的readFile(多参数版本)
fs.readFile(fileName, callback);

// Thunk版本的readFile(单参数版本)
var Thunk = function (fileName) {
  return function (callback) {
    return fs.readFile(fileName, callback);
  };
};

var readFileThunk = Thunk(fileName);
readFileThunk(callback);
复制代码

四、async

1. async 函数其实就是 Generator 函数的语法糖,可以再等待第一阶段得到结果后自动执行第二阶段,而不是像Generator那样手动执行。*换成了async,yield换成了await。

2. async函数对 Generator 函数的改进,体现在以下加点:

  • ①内置执行器
  • ②更语义化
  • ③适应性
  • ④返回promise

3. async函数返回一个 Promise 对象。

async函数内部return语句返回的值,会成为then方法回调函数的参数:

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"
复制代码

如果没有return,则then方法回调函数的参数则得到的是undefined

async function f() {
  await Promise.resolve('hello world'); // 不会执行
}
f().then(a=>{
   console.log(a)})   // undefined
复制代码

4. 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(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"
复制代码

上面代码中,函数getTitle内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,才会执行then方法里面的console.log

5. 一般await后面是接promise对象,返回该对象的结果,如果不是promise对象,则直接返回对应的值:

async function f() {
  // 等同于
  // return 123;
  return await 123;  // return要放await前面,否则会报错
}

f().then(v => console.log(v))
// 123
复制代码

6. await后面的promise状态如果为reject,则会被catch到:

async function f() {
  await Promise.reject('出错了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了
复制代码

任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。

async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}
复制代码

为了防止有错误或reject中断代码的执行,则需要使用catch来处理,或者使用try catch:

async function f() {
  await Promise.reject('出错了')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world
复制代码

如果有多个await命令,可以统一放在try…catch结构中:

async function main() {
  try {
    const val1 = await firstStep();
    const val2 = await secondStep(val1);
    const val3 = await thirdStep(val1, val2);

    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err);
  }
}
复制代码

7. 使用async注意点:

  • ①catch错误,防止代码中断
  • ②对于不存在继发关系的异步操作,应该让它们同步进行,而不是顺序执行:
let foo = await getFoo();
let bar = await getBar();

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
复制代码
  • ③await命令只能用在async函数之中,如果用在普通函数,就会报错,如用在forEach中会报错,因为是并发执行,应该使用for循环:
unction dbFuc(db) { //这里不需要 async
  let docs = [{}, {}, {}];

  // 可能得到错误结果
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
}
复制代码

上面代码可能不会正常工作,原因是这时三个db.post操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用for循环。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  for (let doc of docs) {
    await db.post(doc);
  }
}
复制代码
  • ④async 函数可以保留运行堆栈。
const a = () => {
  b().then(() => c());
};
复制代码

上面代码中,函数a内部运行了一个异步任务b()。当b()运行的时候,函数a()不会中断,而是继续执行。等到b()运行结束,可能a()早就运行结束了,b()所在的上下文环境已经消失了。如果b()或c()报错,错误堆栈将不包括a()。

改成async函数:

const a = async () => {
  await b();
  c();
};
复制代码

上面代码中,b()运行的时候,a()是暂停执行,上下文环境都保存着。一旦b()或c(),错误堆栈将包括a()。

8. async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里:

async function fn(args) {
  // ...
}

// 等同于

function fn(args) {
  return spawn(function* () {
    // ...
  });
}
复制代码

9. 继发/并发顺序输出:

async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}
复制代码

上面代码确实大大简化,问题是所有远程操作都是继发。只有前一个 URL 返回结果,才会去读取下一个 URL,这样做效率很差,非常浪费时间。我们需要的是并发发出远程请求:

async function logInOrder(urls) {
  // 并发读取远程URL
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });

  // 按次序输出
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}
复制代码

10. 异步遍历器:asyncIterator,部署在Symbol.asyncIterator属性上面,最大的语法特点就是调用遍历器的next方法,返回的是一个 Promise 对象。

const asyncIterable = createAsyncIterable(['a', 'b']);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();

asyncIterator
.next()
.then(iterResult1 => {
  console.log(iterResult1); // { value: 'a', done: false }
  return asyncIterator.next();
})
.then(iterResult2 => {
  console.log(iterResult2); // { value: 'b', done: false }
  return asyncIterator.next();
})
.then(iterResult3 => {
  console.log(iterResult3); // { value: undefined, done: true }
});
复制代码

可改写为:

async function f() {
  const asyncIterable = createAsyncIterable(['a', 'b']);
  const asyncIterator = asyncIterable[Symbol.asyncIterator]();
  console.log(await asyncIterator.next());
  // { value: 'a', done: false }
  console.log(await asyncIterator.next());
  // { value: 'b', done: false }
  console.log(await asyncIterator.next());
  // { value: undefined, done: true }
}
复制代码

异步遍历器的next方法是可以连续调用的,不必等到上一步产生的 Promise 对象resolve以后再调用。这种情况下,next方法会累积起来,自动按照每一步的顺序运行下去,所以也可以这样:

const asyncIterable = createAsyncIterable(['a', 'b']);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
const [{
   value: v1}, {
   value: v2}] = await Promise.all([
  asyncIterator.next(), asyncIterator.next()
]);

console.log(v1, v2); // a b
复制代码

11. for await…of

for…of循环用于遍历同步的 Iterator 接口。新引入的for await…of循环,则是用于遍历异步的 Iterator 接口:

async function f() {
  for await (const x of createAsyncIterable(['a', 'b'])) {
    console.log(x);
  }
}
// a
// b
复制代码

如果next方法返回的 Promise 对象被reject,for await…of就会报错,要用try…catch捕捉。

async function () {
  try {
    for await (const x of createRejectingIterable()) {
      console.log(x);
    }
  } catch (e) {
    console.error(e);
  }
}
复制代码

注意,for await…of循环也可以用于同步遍历器:

(async function () {
  for await (const x of ['a', 'b']) {
    console.log(x);
  }
})();
// a
// b
复制代码

12. 异步 Generator 函数

就像 Generator 函数返回一个同步遍历器对象一样,异步 Generator 函数的作用,是返回一个异步遍历器对象:

async function* gen() {
  yield 'hello';
}
const genObj = gen();
genObj.next().then(x => console.log(x));
// { value: 'hello', done: false }
复制代码

13. yield* 语句

yield*语句也可以跟一个异步遍历器:

async function* gen1() {
  yield 'a';
  yield 'b';
  return 2;
}

async function* gen2() {
  // result 最终会等于 2
  const result = yield* gen1();
}
复制代码

与同步 Generator 函数一样,for await…of循环会展开yield*:

(async function () {
  for await (const x of gen2()) {  // 也是相当于执行了gen的遍历器
    console.log(x);
  }
})();
// a
// b
复制代码

最后

因为比较多,所以目前只整理到这里,后续有些比较重要难懂的模块会分开更新,同时包括ES6的部分,希望对你有所帮助,如有不合理的地方欢迎指正,喜欢的就关注一波吧,后续会持续更新。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/101103.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • python 列表转字符串_python怎么把列表转化为字符串「建议收藏」

    python 列表转字符串_python怎么把列表转化为字符串「建议收藏」python列表中的使用str将所有值转换为字符串,以及使用join方法将列表拼接成一个字符串。Pythonjoin()方法用于将序列中的元素以指定的字符连接生成一个新的字符串。(推荐学习:Python视频教程)语法join()方法语法:str.join(sequence)参数sequence–要连接的元素序列。返回值返回通过指定字符连接序列中元素后生成的新字符串。实例>>>ls1=…

  • LabVIEW图像分割算法(基础篇—6)

    LabVIEW图像分割算法(基础篇—6)图像分割是简化机器视觉算法的有效手段之一。它将图像分成一些有意义的区域,以便特征提取过程可基于这些区域提取目标的特征。

  • mybatis的逆向工程怎么实现_mybatisinsert

    mybatis的逆向工程怎么实现_mybatisinsert1.什么是逆向工程mybatis官方提供逆向工程,可以针对单表自动生成mybatis执行所需要的代码(mapper.java、mapper.xml、pojo…)企业实际开发中,常用的逆向工程方式:由数据库的表生成java代码。注意:只能对单表进行操作2.逆向工程的作用myBatis逆向工程可以方便的从数据库中将表自动映射到JAVAPOJO类,并同时生成Mapper.xml和Mapper接…

  • bWAPP练习

    bWAPP练习简介虚拟机下载地址: https://www.vulnhub.com/entry/bwapp-bee-box-v16,53/如果你想自己去部署环境:https://sourceforge.net/projects/bwapp/files/bee-box/bWAPP包含有100多个漏洞,包括OWASPTop10安全风险,很爽的PHPweb靶机。登录username:bee pas…

  • 云服务是免费的吗_云服务器收费

    云服务是免费的吗_云服务器收费近年来,云服务器的普及率快速上升,相当一部分企业从传统服务器转向云服务器,而随着市场的发展,云服务器供应商尤其多,服务器供应商竞争日趋激烈。此时不少服务商表示自己推出永久免费使用的云服务器,面对这样的消息不少企业会感到疑惑,永久免费使用的云服务器究竟是否可信?那么下面就由摩杜云小杜和大家讲一讲有没有永久免费的云服务器。一、首先市场上根本就没有所谓的永久免费使用的云服务器虽然现如今云技术发展快速,但是云资源的成本还是很高的,所以商家为了自己获益,不可能会提供免费的云主机租用服务。但是目前市场上有服务商提供

  • @RestController 和 @Controller 的区别[通俗易懂]

    @RestController 和 @Controller 的区别[通俗易懂]@RestController注解,相当于@Controller+@ResponseBody两个注解的结合,返回json数据不需要在方法前面加@ResponseBody注解了,但使用@RestController这个注解,就不能返回jsp,html页面,视图解析器无法解析jsp,html页面理解下面的注解哦。@ResponseBody表示该方法的返回结果直接写入HTTPrespons…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号