整理 - Promise 和 Async/Await 用法
Promise 和 Async/Await用法整理
1. Promise
1.1 简介
Promise,简单来说就是一个容器,里面保存着某个未来才会结束得事件(通常是一个异步操作得结果)
Promise 对象得基本语法:
new Promise( (resolve, reject) => {
// ...
});
从语法上来说,Promise 是一个对象,从它可以获取异步操作得信息。
基本语法:
let p = new Promise( (resolve, reject) => {
// ...
resolve('success');
});
p.then( result => {
console.log(result); // success
})
Promise对象特点和三个状态:
- Pending
- value -> Fulfilled
- error -> Rejected
例如:
let p = new Promise( (resolve, reject) => {
// ...
resolve('success');
console.log('after resolve');
reject('error');
});
p.then( result => {
console.log(result);
});
p.catch( result => {
console.log(result);
})
结果:
after resolve;
success;
resolve 下面得语句其实是可以执行的,那么为什么reject的状态信息在下面没有接收到呢?这就是因为 Promise 对象的特点:状态的凝固。 new 出一个 Promise 对象时,这个对象的起始状态就是 Pending 状态,在根据 resolve 或 reject 返回 Fulfilled 状态/ Rejected 状态。
1.2 then
Promise.prototype.then
它的作用是为 Promise 实现添加状态改变时的回调函数,参数个数可以有两个。
- then(func1, func2)
- then(func1)
- then(null, func2)
Then 分别接受 resolve 和 reject 的信息,有三种参数形式,第三种比较“怪异”,只用来接受做 reject 处理。
eg:
let p = new Promise( (resolve, reject) => {
// ...
let random = Math.random(); // 小于1大于0
if( random > 0.4 ){
resolve('random > 0.4')
} else {
reject('random <= 0.4')
}
});
p.then( result => {
console.log('resolve', result);
}, result => {
console.log('reject', result);
});
1.3 链式调用
我们来执行一段代码:
let p = new Promise( (resolve, reject) => {
reject('reject');
});
let resultP = p.then(null, result => {
console.log(result);
});
console.log(resultP);
结果:
Promise {
}
reject
js的执行顺序就是这样, 同步 -> 异步 -> 回调,在同步执行的时候,Promise 对象还处于 pending 的状态,也说明了这个 then 返回的是一个 Promise 对象。
而且必须在 then 里面给一个Promise,才能继续调用,否则 undefined。
eg:
let p = new Promise((resolve, reject) => {
reject("error");
});
let resultP = p.then(null, (result) => {
console.log(result);
return new Promise((resolve, reject) => {
reject("error");
});
});
resultP.then(
(tmp) => {
console.log(tmp, 1);
},
(tmp) => {
console.log(tmp, 2);
}
);
结果:
error
error 2
1.4 catch
Promise.prototype.catch
用于指定Promise 状态变为rejected 时的回调函数,可以被认为是then(null, func2)的简写形式。
eg:
let p = new Promise((resolve, reject) => {
reject("error");
});
p.catch((result) => {
console.log(result, 0);
});
p.then(null, (tmp) => {
console.log(tmp, 1);
});
p.catch((result) => {
console.log(result, 2);
});
0
1
2
1.5 Promise.resolve()
将现有对象转为一个resolve状态的 Promise 对象的快捷方式。
传入一个普通的对象:
let p1 = Promise.resolve({
name: "lemon",
age: "xxxx",
});
p1.then((result) => {
console.log(result);
});
如果是Promise对象呢,直接返回
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("success");
}, 500);
});
let pp = Promise.resolve(p);
pp.then((result) => {
console.log(result);
});
console.log(pp === p);
true
success
1.6 Promise.reject()
let p = Promise.reject(123);
console.log(p);
p.then((result) => {
console.log(result);
}).catch((result) => {
console.log("catch", result);
});
Promise {
123 }
catch 123
1.7 Promise.all()
Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。成功和失败的返回的数据类型是不相同的。
eg:
let p1 = Promise.resolve(123);
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("hello");
}, 500);
});
let p3 = Promise.resolve("success");
Promise.all([p1, p2, p3]).then(
(result) => {
console.log(result);
},
(error) => {
console.log(error);
}
);
console.log("---");
'---'
[123, 'hello','success']
成功之后就是数组类型,当所有状态都是成功状态才返回数组,只要其中有一个的对象是 reject 的,就返回 reject 的状态值。
eg:
let p1 = Promise.resolve(123);
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("hello");
}, 500);
});
let p3 = Promise.resolve("success");
Promise.all([p1, p2, p3]).then(
(result) => {
console.log(result);
},
(error) => {
console.log(error);
}
);
'hello'
有一个eg:
//用sleep来模仿浏览器的AJAX请求
function sleep(wait) {
return new Promise((res, rej) => {
setTimeout(() => {
res(new Date());
}, wait);
});
}
let p1 = sleep(5000);
let p2 = sleep(10000);
let p3 = sleep(20000);
Promise.all([p1, p2, p3]).then((result) => {
console.log(result);
//.....
//loading
});
58:55
59:01
59:11
可以看出 Promise.all 中的 Promise 几乎是同时开始的
1.8 Promise.race()
Promise.race 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
和 all 同样接受多个对象 , 不同的是,race()接受的对象中,那个对象返回的快就返回那个对象,就如race直译的赛跑这样。如果对象其中有 reject 状态的,必须 catch 捕捉到,如果返回的够快,就返回这个状态。race 最终返回的只有一个值。
eg:
let p1 = sleep(500);
let p0 = sleep(2000);
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("error");
}, 1000);
});
Promise.race([p1, p0]).then((result) => {
console.log(result);
});
Promise.race([p2, p0])
.then((result) => {
console.log(result);
})
.catch((result) => {
console.log(result);
});
Promise.race([p1, p2])
.then((result) => {
console.log(result);
})
.catch((result) => {
console.log(result);
});
500
500
error
1.9 异常处理
错误处理的声音实在安静,安静得听不见 from Nolan Lawson
当 promise 被明确拒绝时,会发生拒绝;但是如果是在构造函数回调中引发得错误,则会隐式拒绝。
为什么说安静,一个例子,Promise 内部得错误外界用try-catch捕捉不到。可用用then或catch捕获到。
eg:
try {
let p = new Promise((resolve, reject) => {
throw new Error(" I'm error");
});
p.then(
(res) => {
console.log("resolve", res);
},
(error) => {
console.log("reject", error);
p.catch((reject) => {
console.log("Promise.catch");
});
}
);
} catch (error) {
console.log("catch", error);
}
reject Error: I'm error
Promise.catch
所以:
建议,在 promise的链的尾部必须要有个 catch 接着 。
2. Async-Await
2.1简介
Async-Await:
异步编程的最高境界,就是根本不用关心它异步。async 函数就是隧道尽头的亮光,很多人认为它是异步操作的终极解决方案。
async-await 与 promise 的关系:
不存在谁替代谁,因为 async-await 是寄生于 Promise。Generator 的语法糖。
async 和 await 在干什么,async 用于声明一个 function 是异步的,而 await 可以认为是 async wait 的简写, 等待一个异步方法执行完成。
2.2 基本语法
async function demo(params) {
}
demo();
async 函数返回的是一个 Promise 对象。
必须了解的 asyncFunction
console.log(async function(){}.constructor);
在Chrome里申明这样一个函数,可以在控制台看到返回的其实就是一个Promise对象。
扩展需要了解的就是Chrome现在也支持asyncFunction,可以在Chrome控制台测试:
console.log(async function(){}.constructor);
ƒ AsyncFunction() { [native code] }
2.3 规则
- async 表示这是一个 async 函数, await 只能用在这个函数里面。
- await 表示在这里等待 promise 返回结果后,再继续执行。
- await 后面跟着的应该是一个 promise 对象(当然,其他返回值也没关系,只是会立即执行,不过那样就没有意义了...)
await放在普通函数里是会报错的。
await可以理解为 async wait 的简写。await 必须出现在 async 函数内部,不能单独使用。
await等待的虽然是 promise 对象,但不必写.then(...),直接可以得到返回值。
eg:
async function demo() {
let result = await Promise.resolve(123);
console.log(result);
}
demo();
2.4 应用
Promise 虽然一方面解决了 callback 的回调地狱,但是相对的把回调“纵向发展”了,形成了一个回调链。
eg:
function sleep(wait) {
return new Promise((res, rej) => {
setTimeout(() => {
res(wait);
}, wait);
});
}
sleep(100)
.then((result) => {
return sleep(result + 100);
})
.then((result02) => {
return sleep(result02 + 100);
})
.then((result03) => {
console.log(result03);
});
300
后面的结果都是依赖前面的结果。
改成 async/await 写法就是:
function sleep(wait) {
return new Promise((res, rej) => {
setTimeout(() => {
res(wait);
}, wait);
});
}
async function demo() {
let result01 = await sleep(100);
// 上一个await执行之后才会执行下一句
let result02 = await sleep(result01 + 100);
let result03 = await sleep(result02 + 100);
return result03;
}
demo().then((result) => {
console.log(result);
});
因为 async 返回的也是 promise 对象,所以用 then 接受就行了。
300
需要注意的就是 await 是强制把异步变成了同步,这一句代码执行完,才会执行下一句。
2.5 错误处理
既然 .then(...) 不用写了,那么 .catch(...) 也不用写,可以直接用标准的 try catch 语法捕捉错误。
async function demo() {
let result01 = await sleep(100);
// 上一个await执行之后才会执行下一句
let result02 = await sleep(result01 + 100);
let result03 = await sleep(result02 + 100);
try {
let result04 = await new Promise((resolve, reject) => {
setTimeout(() => {
reject(100);
}, 100);
});
return result04;
} catch (e) {
console.log("error", e);
}
}
let p = demo().then(
(data) => {
console.log(data, "1");
},
(e) => {
console.log(e, "2");
}
);
结果:
error 100
undefined "1"
这是基本的错误处理,但是当内部出现一些错误时,包括有一个Promise reject 了,就会抛出错误且下面的代码不会执行。
function sleep(wait) {
return new Promise((res, rej) => {
setTimeout(() => {
rej(wait);
}, wait);
});
}
async function demo() {
let a = await sleep(100);
let b = await sleep(200);
console.log(2);
}
demo();
Uncaught (in promise) 100
处理异常时,如果 async 中已经处理过了则 .catch()将捕捉不到。如果 async 没有处理,则 .catch()可以捕捉到。
eg. async 中处理:
function sleep(wait) {
return new Promise((res, rej) => {
setTimeout(() => {
rej(wait);
}, wait);
});
}
async function demo() {
try {
let result04 = await sleep(100);
} catch (e) {
console.log("error", e);
}
}
let p = demo().catch((e) => {
console.log("promise catch", e);
});
'error' 100
eg. async 外部处理:
function sleep(wait) {
return new Promise((res, rej) => {
setTimeout(() => {
rej(wait);
}, wait);
});
}
async function demo() {
let result04 = await sleep(100);
}
let p = demo().catch((e) => {
console.log("promise catch", e);
});
'promise catch' 100
2.6 注意你的并行执行和循环
如果这三个是你想异步发出的AJAX请求,在这段代码里其实是同步的,第一个发出去被处理后( resolve/reject )才会处理第二个,所以 async / await 需要谨慎使用。
eg.1
function sleep(wait) {
return new Promise((resolve, rej) => {
console.log(`wait: ${wait}`);
setTimeout(() => {
resolve(wait);
console.log(`resolve: ${wait}`);
}, wait);
});
}
async function demo() {
let result01 = await sleep(10000);
let result02 = await sleep(20000);
let result03 = await sleep(30000);
}
let p = demo().catch((e) => {
console.log("promise catch", e);
});
wait: 10000
一段时间后
resolve: 10000
wait: 20000
一段时间后
resolve: 20000
wait: 30000
一段事件后
resolve: 30000
eg. 2
function sleep(wait) {
return new Promise((resolve, rej) => {
console.log(`wait: ${wait}`);
setTimeout(() => {
resolve(wait);
console.log(`resolve: ${wait}`);
}, wait);
});
}
function sleep2(wait) {
return new Promise((resolve, rej) => {
console.log(`wait: ${wait}`);
setTimeout(() => {
rej(wait);
console.log(`reject: ${wait}`);
}, wait);
});
}
async function demo() {
let result01 = await sleep(100);
try {
let result02 = await sleep2(200);
} catch (error) {
let result03 = await sleep(300);
}
}
let p = demo().catch((e) => {
console.log("promise catch", e);
});
'wait:' 100
'resolve': 100
'wait': 200
'reject': 200
'wait': 300
'resolve': 300
由于错误在内部处理没有作为 Promise 抛出所以 demo().catch() 捕捉不到。
async 中异步任务
在 async 中使用 Promise.all([ ]) 生成一个新的 Promise,来达到异步任务效果。
function sleep(wait) {
return new Promise((resolve, rej) => {
console.log(`wait: ${wait}`);
setTimeout(() => {
resolve(wait);
console.log(`resolve: ${wait}`);
}, wait);
});
}
async function demo() {
let res = Promise.all([(sleep(1000), sleep(1000), sleep(1000))]);
}
let p = demo().catch((e) => {
console.log("promise catch", e);
});
'wait:' 10000
'wait:' 10000
'wait:' 10000
一段时间后
resolve: 10000 * 3 (几乎同时)
现有一些 forEach 或者 map 的循环里,比如在 forEach 里使用 await,这时候的上下文就变成了 array,而不是 async function,就会报错。这时候你就要想到是什么错误。