1、Promise
1.1 Promise是什么?
先来看一段代码:
const https = require('https');
function httpPromise(url){
return new Promise(function(resolve,reject){
https.get(url, (res) => {
resolve(data);
}).on("error", (err) => {
reject(error);
});
})
}
httpPromise().then( function(data){
// todo
}).catch(function(error){ //todo })
httpPromise(url1)
.then(res => {
console.log(res);
return httpPromise(url2);
})
.then(res => {
console.log(res);
return httpPromise(url3);
})
.then(res => {
console.log(res);
return httpPromise(url4);
})
.then(res => console.log(res));。
从上面的例子可以看出,Promise 会接收一个执行器,在这个执行器里,我们需要把目标的异步任务给”填进去“。
在 Promise 实例创建后,执行器里的逻辑会立刻执行,在执行的过程中,根据异步返回的结果,决定如何使用 resolve 或 reject 来改变 Promise实例的状态。
1.2 Promise 实例有三种状态:
- 等待中(pending):表示进行中。这是 Promise 实例创建后的一个初始态;
- 完成了 (resolved):表示成功完成。这是我们在执行器中调用 resolve 后,达成的状态;
- 拒绝了(rejected):表示操作失败、被拒绝。这是我们在执行器中调用 reject后,达成的状态;
一旦从等待状态变成为其他状态就永远不能更改状态了,也就是说一旦状态变为 resolved 后,就不能再次改变。
pending->resolved 或 pending->rejected变化不可逆。
// 刚定义时,状态默认为 pending
const p1 = new Promise((resolve, reject) => {
})
// 执行 resolve() 后,状态变成 resolved
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
})
})
// 执行 reject() 后,状态变成 rejected
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject()
})
})
// 直接返回一个 resolved 状态
Promise.resolve(100)
// 直接返回一个 rejected 状态
Promise.reject('some error')
new Promise((resolve, reject) => {
resolve('success')
// 无效
reject('reject')
})
当我们在构造 Promise
的时候,构造函数内部的代码是立即执行的。
new Promise((resolve, reject) => {
console.log('new Promise')
resolve('success')
})
console.log('finifsh')
// new Promise -> finifsh
1.3 状态和 then catch
状态变化会触发 then catch
- pending 不会触发任何 then catch 回调
- 状态变为 resolved 会触发后续的 then 回调
- 状态变为 rejected 会触发后续的 catch 回调
then catch 会继续返回 Promise ,此时可能会发生状态变化!!!
// then() 一般正常返回 resolved 状态的 promise
Promise.resolve().then(() => {
return 100
})
// then() 里抛出错误,会返回 rejected 状态的 promise
Promise.resolve().then(() => {
throw new Error('err')
})
// catch() 不抛出错误,会返回 resolved 状态的 promise
Promise.reject().catch(() => {
console.error('catch some error')
})
// catch() 抛出错误,会返回 rejected 状态的 promise
Promise.reject().catch(() => {
console.error('catch some error')
throw new Error('err')
})
2、Promise常见题
2.1 Promise特点
Promise 的特点是什么,分别有什么优缺点?什么是 Promise 链?Promise 构造函数执行和 then 函数执行有什么区别?
特点:实现链式调用
优点:Promise
很好地解决了回调地狱的问题
缺点:比如无法取消 Promise
,错误需要通过回调函数捕获。
Promise链:
- 每次调用 then 之后返回的都是一个全新的Promise,因此又可以接着使用then方法,由此形成promise链
- 在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装
Promise 构造函数执行和 then 函数执行有什么区别:
- 构造 Promise 的时候,构造函数内部的代码是立即执行的
- then函数在promise.resolve()执行后执行
2.2 代码题
// 第一题
Promise.resolve().then(() => {
//没报错返回resolved状态
console.log(1) // 1
}).catch(() => {
console.log(2) // 不会走
}).then(() => {
console.log(3) // 3
}) // resolved
//结果 1 3
// 第二题
Promise.resolve().then(() => {
console.log(1)
// 返回 rejected 状态的 promise
throw new Error('erro1')
}).catch(() => {
// 返回 resolved 状态的 promise
console.log(2)
}).then(() => {
console.log(3)
})
//结果 1 2 3
// 第三题
Promise.resolve().then(() => { // 返回 rejected 状态的 promise
console.log(1)
throw new Error('erro1')
}).catch(() => { // 返回 resolved 状态的 promise
console.log(2)
}).catch(() => {
console.log(3)
})
//结果 1 2
3、Generator
Generator是什么:
Generator 一个有利于异步的特性是,它可以在执行中被中断、然后等待一段时间再被我们唤醒。通过这个“中断后唤醒”的机制,我们可以把 Generator看作是异步任务的容器,利用 yield 关键字,实现对异步任务的等待。
function firstAjax() {
ajax(url1, () => {
// 调用secondAjax
secondAjax()
})
}
function secondAjax() {
ajax(url2, () => {
// 处理逻辑
})
}
ajax(url, () => {
// 调用firstAjax
firstAjax()
})
我们可以通过 Generator
函数解决回调地狱的问题,可以把之前的回调地狱例子改写为如下代码:
function *fetch() {
yield ajax(url, () => {})
yield ajax(url1, () => {})
yield ajax(url2, () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
发现Generator
确实有点麻烦,每次迭代都要.next()才能继续下一步的操作,直到done为true时停止。所以我们利用一个第三方库(co)直接执行:
co是什么:generator函数(生成器函数)的自动执行函数。
const co = require('co');
co(httpGenerator());
4、async/await
4.1 async/await产生背景
- 异步回调callback
- Promise then catch链式调用,但也是基于回调函数
- async/await是同步语法,彻底消灭回调函数
4.2 使用
一个函数如果加上 async
,那么该函数就会返回一个 Promise
async function test() {
return "1"
}
console.log(test()) // -> Promise {<resolved>: "1"}
async
就是将函数返回值使用 Promise.resolve()
包裹了下,和 then
中处理返回值一样,并且 await
只能配套 async
使用。
用同步方法编写异步。
function loadImg(src) {
const promise = new Promise((resolve, reject) => {
const img = document.createElement('img')
img.onload = () => {
resolve(img)
}
img.onerror = () => {
reject(new Error(`图片加载失败 ${src}`))
}
img.src = src
})
return promise
}
async function loadImg1() {
const src1 = 'http:xxx.png'
const img1 = await loadImg(src1)
return img1
}
async function loadImg2() {
const src2 = 'https://xxx.png'
const img2 = await loadImg(src2)
return img2
}
(async function () {
// 注意:await 必须放在 async 函数中,否则会报错
try {
// 加载第一张图片
const img1 = await loadImg1()
console.log(img1)
// 加载第二张图片
const img2 = await loadImg2()
console.log(img2)
} catch (ex) {
console.error(ex)
}
})()
4.3 典型场景
4.3.1 并发
下面代码模拟了三个请求接口,也就是三个请求没有任何依赖关系,却要等到上一个执行完才执行下一个,带来时间上的浪费。
(async () => {
const getList1 = await getList1();
const getList2 = await getList1();
const getList3 = await getList2();
})();
解决方案:
(async () => {
const listPromise = getList();
const anotherListPromise = getAnotherList();
await listPromise;
await anotherListPromise;
})();
// 也可以使用
(async () => {
Promise.all([getList(), getAnotherList()]).then(...);
})();
4.3.2 捕获错误
使用 try catch 捕获错误,当我们需要捕获多个错误并做不同的处理时,try catch 会导致代码杂乱:
async function asyncTask(cb) {
try {
const res1 = await request1(resByRequest1); //resByRequest1返回值为promise
if(!res1) return cb('error1');
} catch(e) {
return cb('error2');
}
try {
const res2 = await request2(resByRequest2); //resByRequest2返回值为promise
} catch(e) {
return cb('error3');
}
}
简化错误捕获:添加一个中间函数:
export default function to(promise) {
return promise.then(data => {
return [null, data];
})
.catch(err => [err]);
}
错误捕获的代码:
async function asyncTask() {
let err, data
[err, data1] = await to(resByRequest1);
if(!data1) throw new Error('xxxx');
[err, data2] = await to(resByRequest2);
if(!data2) throw new Error('xxxx');
}
5、async/await和Promise的关系
- async 函数返回结果都是 Promise 对象(如果函数内没返回 Promise ,则自动封装一下)
- await相当于Promise的then
- try…catch可捕获异常,代替了Promise的catch
async function fn2() {
return new Promise(() => {})
}
console.log( fn2() )
async function fn1() { // 执行async函数,返回的是一个Promise对象
return 100
}
console.log( fn1() ) // 相当于 Promise.resolve(100)
- await 后面跟 Promise 对象:会阻断后续代码,等待状态变为 resolved ,才获取结果并继续执行
- await 后续跟非 Promise 对象:会直接返回
(async function () {
const p1 = new Promise(() => {})
await p1
console.log('p1') // 不会执行
})()
(async function () {
const p2 = Promise.resolve(100)
const res = await p2
console.log(res) // 100
})()
(async function () {
const res = await 100
console.log(res) // 100
})()
(async function () {
const p3 = Promise.reject('some err')
const res = await p3
console.log(res) // 不会执行
})()
try…catch 捕获 rejected 状态
(async function () {
const p4 = Promise.reject('some err')
try {
const res = await p4
console.log(res)
} catch (ex) {
console.error(ex)
}
})()
总体来看:
- async 封装 Promise
- await 处理 Promise 成功
- try…catch 处理 Promise 失败
6、异步本质
await 是同步写法,但本质还是异步调用。
async function async1 () {
console.log('async1 start') // 2
await async2()
console.log('async1 end') // 5
}
async function async2 () {
console.log('async2') // 3
}
console.log('script start') // 1
async1()
console.log('script end') //4
7、async和await常见题
async 及 await 的特点,它们的优点和缺点分别是什么?await 原理是什么?
特点:
- 一个函数如果加上async 那么其返回值是Promise,async 就是将函数返回值使用 Promise.resolve() 进行包裹,和then处理返回值一样
- await只能配合async使用 不能单独使用
优点:
- 相比于Promise来说优势在于能够写出更加清晰的调用链,并且也能优雅的解决回调地狱的问题
缺点:
- 因为await将异步代码变成了同步代码,如果多个异步之间没有关系,会导致性能降低
原理:
- await 就是 generator 加上 Promise 的语法糖,且内部实现了自动执行 generator