ES6的异步起始
2019-03-30 本文已影响0人
hellomyshadow
Promise
-
Promise
是ES6引入的一种异步编程的解决方案; -
Promise
是一个容器,保存着一个事件的结果,此事件通常是一个未来才会结束的异步操作; -
Promise
的特点:- 对象的状态不受外界影响;
-
Promise
表示一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)、rejected
(已失败) - 只有异步操作的结果才可以决定当前处于哪种状态,其他任何操作都无法改变Promise的状态。
-
- 一旦状态变化,就不会再变;
-
Promise
的状态改变只有两种可能:pending-->fulfilled
,pending-->rejected
- 一旦状态变化,就不会在变化了,如果再对
Promise
添加回调函数,也会立即得到结果。
-
- 无法取消
Promise
,一旦新建就立即执行,无法中途取消; - 如果不设置回调函数,
Promise
内部会抛出异常,但不会反映到外部; - 当处于
pending
状态时,无法得知目前进展到哪一个阶段:刚刚开始还是即将执行完成
- 对象的状态不受外界影响;
- 基本使用
const promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } // ... somo code --->并不会因为状态被resolve()和reject()修改了而不执行了 });
-
Promise
接受一个函数作为参数,此函数的两个参数resolve、reject
也是函数,由JS引擎提供; -
resolve()、reject()
的作用分别是将pending状态变为resolved、rejected
,并将异步操作的结果作为参数传递出去; -
Promise
新建后会立即执行,然后使用then()
获取执行的状态和结果;
promise.then(function(res){ -->Promise状态变为resolved时调用,res为 resolve() 传出的参数 //成功 }, function(err){ --->可选,Promise状态变为rejected时调用,err为 reject() 传出的参数 //失败 });
-
resolve()、reject()
只是修改了Promise
的状态,并不会终结Promise
参数函数的执行,
但通常不会在resolve()、reject()
后再执行代码了。
new Promise((resolve, reject) => { return resolve(1); // ... somo code --> 不会再执行了 })
-
-
Promise.prototype.then()
-
then()
返回一个新的Promise
实例,所以支持链式调用
promise.then(function(res){ --->第一个then return res.post; }).then(function(post){ -->第二个then console.log(post); });
- 第二个
then()
的回调函数中,接收的是第一个then()
的回调函数返回的结果; - 如果第一个
then()
的回调函数返回一个Promise
实例,
那么第二个then()
会等待这个Promise
的状态变化之后,才会执行其回调函数;
promise.then(res=>{ return axiosPost(res.post); ---> axiosPost()返回一个Promise对象 }).then(post=>{ console.log(post); --> axiosPost()的状态变为resolved }, err=>{ console.log(post); --> axiosPost()的状态变为rejected });
-
-
Promise.prototype.catch()
-
catch()
其实就是then(null/undefined, rejected)
的别名,单独指定发生错误的回调; -
reject()
是手动把状态变为rejected
,如果Promise
中的异步操作抛出异常,状态也会变为rejected
const promise = new Promise((resolve, reject) => { throw new Error('test'); --> 等效于:reject(new Error('test')); }); promise.catch(function(err) { console.log(err); });
- 但是,如果
Promise
的状态已经变成了resolved
,再抛出异常是无效的,因为状态一旦改变就无法修改;
new Promise(function(resolve, reject) { resolve('ok'); throw new Error('test'); //无效 }).then(value=>{ console.log(value); //执行成功的回调 }).catch(err => { console.log(error); });
-
Promise
对象的错误具有冒泡
性质,会一直向后传递,直到被捕获为止;
也就是说,异步执行失败时,如果前一个then()
没有失败的回调函数,则错误会一直向下传递;
所以,不要在then()
中定义rejected状态的回调函数,而是在最后使用catch()
捕获异常。 -
then()
和catch()
也是有顺序的,如果catch()
还有then()
,且此then()
抛出了异常,那只能等待后面的catch()
捕获。 - 当然,即使没有
catch()
,也没有任何rejected
状态的回调函数,Promise
也不会抛出任何终止程序的异常。
new Promise(function(resolve, reject) { resolve(x + 2); //x没有声明,报错 }).then(function() { console.log('everything is great'); }); setTimeout(() => { console.log(123) }, 2000); //正常执行
-
Node
计划改进Promise
的rejected
状态机制,如果未捕获异常,则终止程序执行。
-
-
Promise.prototype.finally()
-
ES2018
引入的finally()
,不管Promise
对象最后的状态如何,都会执行;
promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});
-
finally
本质上是then
方法的特例
promise.then(result => { // 语句 return result; }, error => { // 语句 throw error; });
-
-
Promise.all()
:将多个Promise
实例,包装成一个新的Promise
实例-
Promise.all()
接受一个Promise
实例的数组作为参数; - 如果参数不是
Promise
实例,则调用Promise.resolve()
将其转为Promise
实例;
const p = Promise.all([p1, p2, p3]);
-
p
的状态由p1、p2、p3
决定:- 只有
p1、p2、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
p1、p2、p3
的返回值组成一个数组,传递给p
的回调函数 - 只要
p1、p2、p3
之中有一个被rejected
,p
的状态就变成rejected
第一个被reject
的实例的返回值,会传递给p的回调函数
- 只有
- 如果
p1/p2/p3
定义了catch()
,那么它一旦被rejected
,会返回一个新的Promise
实例,并不会触发Promise.all()
的catch()
-
-
Promise.race()
- 也是将一个
Promise
实例的数组包装成一个新的Promise
实例;
const p = Promise.race([p1, p2, p3]);
- 只要
p1、p2、p3
中有一个改变状态,p的状态就立即改变。第一个状态发生改变的Promise
实例的返回值,会传递给p
的回调函数;
- 也是将一个
-
Promise.resolve()
:将现有对象转为Promise
对象
Promise.resolve('foo');
--->等价于:new Promise(resolve => resolve('foo'));
- 如果参数就是
Promise
实例,则直接返回; - 如果参数是
thenable
对象,thenable
对象指的是具有then
方法的对象
let thenable = { then: function(resolve, reject) { resolve(42); } };
1. Promise.resolve() 会将这个对象转为 Promise 对象,然后立即执行其then() 2. 此时,Promise对象的状态已经定型了,再使用 then() 时,会立即执行;
let p1 = Promise.resolve(thenable); p1.then(value=>{ ------> 立即执行 console.log(value); // 42 });
- 如果参数是普通对象或不是对象,则将其转为一个状态已是
resolved
的Promise
对象; - 如果不带有任何参数,则直接返回一个resolved状态的
Promise
对象; - 立即
resolve
的Promise
对象,是在本轮 事件循环 结束时,而不是在下一轮 事件循环 开始时。
- 如果参数就是
-
Promise.reject()
:返回一个状态为rejected
的Promise
实例- 与
Promise.resolve()
不同,Promise.reject()
的参数会原封不动的传递给rejected
状态的回调函数;
const thenable = { then(resolve, reject) { reject('出错'); } }; Promise.reject(thenable).catch(e => { console.log(e === thenable); //true,说明回调参数e并不是"出错",而是整个对象 })
- 与
-
Promise.try()
- 事实上,
Promise.try
就是模拟try
代码块,就像promise.catch
模拟的是catch
代码块
Promise.resolve().then(fn);
- 如果
fn
是同步函数,经过Promise
包装,fn
就变成了异步执行,在本轮事件循环的末尾执行;
const f = () => console.log('now'); Promise.resolve().then(f); console.log('next'); // next // now
- 让同步函数同步执行,异步函数异步执行,两种写法:
- 第一种
const f = () => console.log('now'); (async () => f())(); //立即执行的匿名函数 console.log('next');
1. async () => f()会吃掉 f() 抛出的错误,要使用promise.catch()捕获错误
(async () => f())() .then(...) .catch(...)
2. 第二种
const f = () => console.log('now'); ( () => new Promise( resolve => resolve(f()); //立即执行的匿名函数 ) )(); console.log('next');
-
Promise.try()
就是用来解决此类问题的标准
const f = () => console.log('now'); Promise.try(f); console.log('next');
-
Promise.try()
为所有操作提供了统一的处理机制,其中一点就是可以更好地管理异常
database.users.get({id: uId}) .then(...) .catch(...);
-
database.users.get()
返回一个Promise
对象,如果抛出异步错误,则用catch()
捕获; - 但如果
database.users.get()
抛出同步错误,则需要try-catch
捕获
try { database.users.get({id: uId}) .then(...) .catch(...); } catch(e){ ... }
- 而
Promise.try()
则可以让promise.catch()
统一捕获异常
Promise.try(() => database.users.get({id: userId})) .then(...) .catch(...)
- 事实上,
Iterator
-
Iterator
:遍历器,是一种接口,为各种不同的数据结构提供统一的访问机制; - 任何数据结构只要部署
Iterator
接口,就可以完成遍历操作,即依次处理该数据结构的所有成员; -
Iterator
的三种作用- 为各种数据结构,提供一个统一的、简便的访问接口;
- 使得数据结构的成员能够按某种次序排列;
-
Iterator
接口主要供 ES6新增的for-of
消费。
-
Iterator
的遍历过程- 遍历器对象本质上是一个指针对象,指向当前数据结构的起始位置;
- 第一次调用指针对象的
next()
,可以将指针指向数据结构的第一个成员; - 第二次调用指针对象的
next()
,指针就指向数据结构的第二个成员; - 不断调用指针对象的
next()
,直到它指向数据结构的结束位置. - 每一次调用
next()
,都会返回数据结构的当前成员的信息,一个包含value
和done
两个属性的对象; -
value
属性是当前成员的值,done
属性是一个布尔值(表示遍历是否结束); - 模拟
next()
function makeIter(arr) { var nextIndex = 0; return { next: function() { return nextIndex < arr.length ? {value: arr[nextIndex++], done: false} : {value: undefined, done: true}; } }; } var it = makeIter(['a', 'b']); it.next() // { value: "a", done: false } it.next() // { value: "b", done: false } it.next() // { value: undefined, done: true }
-
done:false
和value:undefined
属性是可以省略的.
- ES6 规定:默认的
Iterator
接口部署在数据结构的Symbol.iterator
属性上- 也就是说,一个数据结构只要具有
Symbol.iterator
属性,就可以认为是 可遍历的; -
Symbol.iterator
属性本身是一个函数,是当前数据结构默认的遍历器生成函数;
const obj = { [Symbol.iterator]: function() { -->让obj变成一个可遍历的对象 return { next: function () { return { value: 1, done: true }; } }; } };
- 原生具备
Iterator
接口(部署了Symbol.iterator
属性)的数据结构
Array,Map,Set,String,TypedArray,arguments,NodeList
- 数组的
Symbol.iterator
属性
let arr = ['a', 'b']; let iter = arr[Symbol.iterator](); iter.next() // { value: 'a', done: false } iter.next() // { value: 'b', done: false } iter.next() // { value: undefined, done: true }
- 也就是说,一个数据结构只要具有
- 默认调用
Iterator
接口的场景- 解构赋值:对数组和Set进行解构赋值时,默认调用
Symbol.iterator
方法
let set = new Set().add('a').add('b').add('c'); let [x, ...rest] = set;
- 扩展运算符(三点运算符)
var str = 'hello'; [...str] // ['h','e','l','l','o'] let arr = ['b', 'c']; ['a', ...arr, 'd'] // ['a', 'b', 'c', 'd']
-
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()、Promise.all()、Promise.race()、Map(), Set(), WeakMap(), WeakSet()
- 解构赋值:对数组和Set进行解构赋值时,默认调用
- 遍历器对象的
return(),throw()
- 遍历器对象除了具有 next(),还可以具有
return()
和throw()
- 遍历器对象生成函数,
next()
是必须部署的,return()
和throw()
可选; -
for-of
循环提前退出(break
语句或者出错),就会调用return()
如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return()
let readLine = { [Symbol.iterator]() { return { next() { return { done: false }; }, return() { file.close(); return { done: true }; } }; } }; for (let line of readLine) { console.log(line); break; //break语句 } for (let line of readLine) { console.log(line); throw new Error(); //抛出异常,终止循环 }
-
return()
必须返回一个对象,这是Generator
规格决定的; -
throw()
主要是配合Generator
函数使用,一般的遍历器对象用不到。
- 遍历器对象除了具有 next(),还可以具有
- ES6借鉴
C++、Java、C# 和 Python
,引入了for-of
,作为遍历所有数据结构的统一方法- 一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用 for-of 遍历它的成员;
-
for-of
内部调用的是数据结构的Symbol.iterator
方法;
- 有些数据结构是在现有数据结构的基础上,计算生成的,ES6的
数组、Set、Map
都部署了三个方法-
entries()
:返回一个遍历器对象,用来遍历 [键, 值] 组成的数组;
对于数组,键就是索引值;对于Set
,键与值相同;Map
的Iterator
接口,默认就是调用entries()
let arr = ['a', 'b', 'c']; for (let pair of arr.entries()) { console.log(pair); } // [0, 'a'] // [1, 'b'] // [2, 'c']
-
keys()
:返回一个遍历器对象,用来遍历所有的键名; -
values()
:返回一个遍历器对象,用来遍历所有的键值; - 这三个方法调用后生成的遍历器对象,所遍历的都是计算生成的数据结构。
-
- 对于字符串,
for-of
能正确识别32
位UTF-16
字符; - 伪数组:
字符串、DOM NodeList、arguments
,可以用for-of
遍历- 但并不是所有的伪数组都具备
Iterator
接口
let arrayLike = { length: 2, 0: 'a', 1: 'b' }; // 报错 for (let x of arrayLike) { console.log(x); }
- 最简单的方式是使用
Array.from()
将其转为数组
for (let x of Array.from(arrayLike)) { console.log(x); }
- 但并不是所有的伪数组都具备
-
for,for-in,forEach,for-of
-
for-in
主要为遍历对象而设计,不适用于遍历数组; -
forEach
是数组内置的方法,无法中途跳出循环,break
和return
无效; -
for-of
支持break、continue、return
,循环可遍历对象。
-