一、Promise对象的含义、特点
含义: 异步编程的一种解决方案,传统解决方案通过回调函数和时间,而
Promise
相当于一个容器,里面保存着未来才会结束的事件(通常是一个异步操作)的结果,从语法上说,Promise
是一个对象,可以获取异步操作的消息,提供统一的API,各种异步操作都可以以同样的方法进行处理
特点:(1)对象的状态不受外界影响,有三种状态:pending
(进行中)、fulfilled
(已成功)、rejected
(已失败),只有异步操作的结果可以改变当前的状态,其他任何操作都无法改变这个状态。
(2)状态一旦改变就不会在变了,任何时候都可以得到这个结果。Promise
对象的状态改变只有两种可能,从pending
到fulfilled
,从pending
到rejected
,只要状态发生改变,就会一直保持这个结果,就算再对对象Promise
添加回调函数,也会立即得到这个结果,与事件Event
不同,事件的特带你是如果错过了它,再去监听,是得不到结果的
(3)Promise
一旦新建就会立即执行,无法途中取消,如果设置回调函数,Promise
内部抛出错误,不会反应到外部,不会影响整体函数的执行,处于pending状态时,无法得知当前进展哪一个阶段
基本用法
ES6 规定,Promise
对象是一个构造函数,用来生成Promise
实例。
let promise=new Promise(function (resolve,reject){
setTimeout(resolve,100,1);
});
Promise
构造函数接受一个函数作为参数,该函数的两个参数也是一个函数,分别是resolved
和rejected
,resolved
将Promise
对象的状态从未完成变为成功,成功时的回调函数,会将异步操作的结果,作为参数传递出去,rejected
将Promise
对象的状态由未完成变为失败,失败时调用的函数,也会将异步操作报出的错误,作为参数传递出去
注:
setTimeout(resolve,100,1);
,第三个参数时第一个参数函数的的形参
promise.then(function (res){
console.log(res);//1
},function (arr){
console.log(arr);
}).catch(function (err){
console.log(err);
})
then
方法接受两个回调函数作为参数,第一个回调函数是Promise
对象的状态变为resolved
时调用,第二个回调函数是Promise
对象变为rejected
时调用,这个函数是可选的,不一定要提供,这两个函数都接受Promise对象传出的值作为参数,当Promise
对象的状态发生改变时就会触发then
方法绑定的回调函数
then()
方法的作用是Promise
实例添加解决(fulfillment)和拒绝(rejection)状态的回调函数then()方法会返回一个新的Promise实例,所以then()
方法后面可以继续跟另一个then()
方法进行链式调用
//promise执行的先后顺序
let promise = new Promise(function (resolve, reject) {
console.log("start");
setTimeout(resolve, 1000, "shuju");
});
console.log("外部");
promise.then(function (res) {
console.log(res);
//对于res的操作需要在此代码块写,因为外部获取不到
});
console.log("wan");
图中的数据是其他代码的结果,忽略掉,从结果看就是,Promise一旦新建就立即执行,输入start,与此同时输入同步结果外部、完,当setTimeout(resolve, 1000, "shuju");
执行完后,即Promise状态改变后,resolve函数的结果作为形参传入then()
,并输出结果!
异步加载图片
let promise = new Promise(function (resolve, reject) {
let img = new Image();
img.onload = function () {
resolve(img);
};
img.onerror = function () {
reject(new Error("图片加载失败" + url));
}
});
promise.then(function (res) {
console.log(res);
}).catch(function (err) {
console.log(err);
})
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);
});
多个Promise连用
const promise1=new Promise(function(resolve,reject){
setTimeout(resolve,3000,"a");
})
const promise2=new Promise(function(resolve,reject){
setTimeout(resolve,1000,promise1);
})
promise2.then(function(res){
console.log(res);
})
promise1
和promise2
都是 Promise
的实例,但是promise2
的resolve
方法将promise1
作为参数,即一个异步操作的结果是返回另一个异步操作,promise1
的状态就会传递给promise2
,promise2
的回调函数就会等待promise1
执行完,状态改变后再执行
再来一个例子
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
上面代码中,p1
是一个 Promise
,3 秒之后变为rejected
。p2
的状态在 1 秒之后改变,resolve
方法返回的是p1
。由于p2
返回的是另一个 Promise
,导致p2
自己的状态无效了,由p1
的状态决定p2
的状态。所以,后面的then
语句都变成针对后者(p1
)。又过了 2 秒,p1
变为rejected
,导致触发catch
方法指定的回调函数。
多个then()链式调用
var p2 = new Promise(function (resolve) {
resolve(100);
});
p2.then(function (value) {
return value * 2;
}).then(function (value) {
return value * 2;
}).then(function (value) {
console.log("finally: " + value);
});
调用then()
方法之后都会新建一个promise
对象,这里的return
语句相当于Promise
语句中的resolve()
或者rejected()
,他们的结果,作为then
方法函数的参数传入
多个Promise合并
Promise.all()
Promise.all()
方法用于将多个Promise
实例,包装成一个新的Promise
实例。
const p = Promise.all([p1, p2, p3]);
上面代码中,Promise.all()
方法接受一个数组作为参数,p1
、p2
、p3
都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve
方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()
方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p的状态由p1
、p2
、p3
决定,分成两种情况。
(1)只有p1
、p2
、p3
的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1
、p2
、p3
的返回值组成一个数组,传递给p的回调函数。
(2)只要p1
、p2
、p3
之中有一个被rejected,p的状态就变成rejected,此时第一个被reject
的实例的返回值,会传递给p的回调函数。
如果作为参数的 Promise 实例,自己定义了catch
方法,那么它一旦被rejected
,并不会触发Promise.all()
的catch
方法
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了
上面代码中,p1
会resolved
,p2
首先会rejected
,但是p2
有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch
方法后,也会变成resolved
,导致Promise.all()
方法参数里面的两个实例都会resolved
,因此会调用then
方法指定的回调函数,而不会调用catch方法指定的回调函数。
如果p2
没有自己的catch
方法,就会调用Promise.all()
的catch
方法。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 报错了
Promise.race()
Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
二、Generator 函数的语法
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象。Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态
Generator 函数有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用
yield
表达式,定义不同的内部状态,星号*的位置以下几个都是合法的
//*的位置
//建议使用这一个
function* sum(x){
yield x+(yield x*2);
yield x+3;
}
function * sum(x){
yield x+(yield x*2);
yield x+3;
}
function *sum(x){
yield x+(yield x*2);
yield x+3;
}
调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象。
function* c(){
yield 'hello';
yield 'world';
return 'over';
}
console.log(c);/*ƒ* c(){
yield 'hello';
yield 'world';
return 'over';
}*/
console.log(c());
总结:调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的
next
方法,就会返回一个有着value
和done
两个属性的对象。value
属性表示当前的内部状态的值,是yield
表达式后面那个表达式的值;done
属性是一个布尔值,表示是否遍历结束。yield
表达式是暂停执行的标记,而next
方法可以恢复执行
function* c(){
yield 'hello';
yield 'world';
return 'over';
}
console.log(c);/*ƒ* c(){
yield 'hello';
yield 'world';
return 'over';
}*/
console.log(c());
var d=c();
console.log(d.next());//{value: "hello", done: false}
console.log(d.next());//{value: "world", done: false}
console.log(d.next());//{value: "over", done: true}
//遍历结束的时候,即done为true时,以后的next() value值都为undefined
console.log(d.next());//{value: undefined, done: true}
console.log(d.next());//{value: undefined, done: true}
遍历器对象的next方法的运行逻辑如下
(1)遇到
yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。(2)下一次调用
next
方法时,再继续往下执行,直到遇到下一个yield表达式。(3)如果没有再遇到新的
yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。(4)如果该函数没有
return
语句,则返回的对象的value
属性值为undefined
yield
表达式后面的表达式,只有当调用next
方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”的语法功能
function *sum1(){
yield 12+34;
}
console.log(sum1().next());//46
yield
表达式与return
语句既有相似之处,也有区别
相似之处在于,都能返回紧跟在语句后面的那个表达式的值。
区别在于每次遇到yield
,函数暂停执行,下一次再从该位置继续向后执行,而return
语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return
语句,但是可以执行多次(或者说多个)yield
表达式。正常函数只能返回一个值,因为只能执行一次return
;
复杂一点的例子
function * sum(x){
yield x+(yield x*2);
}
var sums=sum(3);
//第一种
console.log(sums.next());//6 false
console.log(sums.next());//NAN false
console.log(sums.next());//undefined true
//第二种
console.log(sums.next());//6 false
console.log(sums.next(90));//93 false
console.log(sums.next());//undefined true
世纪大难题!!!被自己写的函数难住了!!!
这里需要注意的是
yield
表达式如果用在另一个表达式之中,必须放在圆括号里面。
yield
表达式用作函数参数或放在赋值表达式的右边,可以不加括号。
yield
表达式只能用在 Generator 函数里面,用在其他地方都会报错。
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
一旦next
方法的返回对象的done
属性为true
,for...of
循环就会中止,且不包含该返回对象,所以上面代码的return
语句返回的6,不包括在for...of
循环之中。
yield*表达式
function* bar() {
yield 'x';
yield* foo();
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"
function* inner() {
yield 'hello!';
}
function* outer1() {
yield 'open';
yield inner();
yield 'close';
}
var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一个遍历器对象
gen.next().value // "close"
function* outer2() {
yield 'open'
yield* inner()
yield 'close'
}
var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
let delegatedIterator = (function* () {
yield 'Hello!';
yield 'Bye!';
}());
let delegatingIterator = (function* () {
yield 'Greetings!';
yield* delegatedIterator;
yield 'Ok, bye.';
}());
for(let value of delegatingIterator) {
console.log(value);
}
// "Greetings!
// "Hello!"
// "Bye!"
// "Ok, bye."
//yield*后面的 Generator 函数(没有return语句时),等同于在 Generator 函数内部,部署一个for...of循环。
function* concat(iter1, iter2) {
yield* iter1;
yield* iter2;
}
// 等同于
function* concat(iter1, iter2) {
for (var value of iter1) {
yield value;
}
for (var value of iter2) {
yield value;
}
}
三、async函数
async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。
async
函数的await
命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即resolved
的 Promise 对象)。
async
函数的返回值是 Promise 对象
async
函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await
命令就是内部then
命令的语法糖。
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
例子:下面代码指定 50 毫秒以后,输出hello world
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
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 () => {};
返回Promise
对象
async
函数返回一个 Promise 对象。
async
函数内部return
语句返回的值,会成为then
方法回调函数的参数。
async
函数返回的 Promise 对象,必须等到内部所有await
命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return
语句或者抛出错误,也就是说,只有async
函数内部的异步操作执行完,才会执行then
方法指定的回调函数。
await
命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值
await
命令后面是一个thenable
对象(即定义then
方法的对象),那么await
会将其等同于 Promise 对象。
await
命令后面的 Promise 对象如果变为reject
状态,则reject
的参数会被catch
方法的回调函数接收到
async function f() {
return 'hello world';
}
f().then(v => console.log(v))
// "hello world"
任何一个
await
语句后面的 Promise 对象变为reject
状态,那么整个async
函数都会中断执行
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
最好把
await
命令放在try...catch
代码块中
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另一种写法
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err);
});
}
多个
await
命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
let foo = await getFoo();
let bar = await getBar();
上面代码中,getFoo
和getBar
是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo
完成以后,才会执行getBar
,完全可以让它们同时触发。
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
上面两种写法,getFoo
和getBar
都是同时触发,这样就会缩短程序的执行时间。
await
命令只能用在async
函数之中,如果用在普通函数,就会报错。
async function dbFuc(db) {
let docs = [{}, {}, {}];
// 报错
docs.forEach(function (doc) {
await db.post(doc);
});
}
上面代码会报错,因为await
用在普通函数之中了。但是,如果将forEach
方法的参数改成async
函数,也有问题。
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);
}
}
如果确实希望多个请求并发执行,可以使用Promise.all
方法。当三个请求都会resolved
时,下面两种写法效果相同。
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = await Promise.all(promises);
console.log(results);
}
// 或者使用下面的写法
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = [];
for (let promise of promises) {
results.push(await promise);
}
console.log(results);
}