-
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
接口主要供 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()
- 遍历器对象的
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
函数使用,一般的遍历器对象用不到。
- 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
,循环可遍历对象。
网友评论