Promise与async/await
Promise与async/await在于解决回调地狱而出现,他们两者有点不同,执行async函数返回的是Promise对象,而async相当于Promise的then。
Promise
使用 JavaScript 编写代码会大量的依赖异步计算,计算那些我们现在不需要但将来某时候可能需要的值。所以 ES6 引入了一个新的概念,用于更简单地处理异步任务:Promise。
Promise对象是对我们现在尚未得到但将来会得到值的占位符;它是对我们最终能够得知异步计算结果的一种保证。如果我们兑现了我们的承诺,那结果会得到一个值。如果发生了问题,结果则是一个错误,一个为什么不能交付的借口。使用Promise的一个最佳例子是从服务器获取数据:我们要承诺最终会拿到数据,但其实总有可能发生错误。
一个Promise必然处于以下几种状态之一:
- 待定(pending) :初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled) :意味着操作成功完成。
- 已拒绝(rejected) :意味着操作失败。
下面是Promise的流程图:
Promise的用法
下面是Promise的基础用法,setTimeout模拟的是异步请求,因为setTimeout里调用了resolve,此时的Promise状态为resolved,请求成功之后执行then里面的内容。
const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('resolve');
    });
});
p1.then((res) => {
    console.log('p1 res:' + res);
    console.log(p1);
});
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('resolve');
    });
}).then((res) => {
    console.log('p2 res:' + res);
    console.log(p2);
});
const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('resolve');
        reject('reject');
    });
}).then((res) => {
    console.log('p3 res:' + res);
    console.log(p3);
});
const p4 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('resolve');
        reject('reject');
    });
}).then((res) => {
    console.log('p4 res:' + res);
    console.log(p4);
}).catch((err) => {
    console.log('p4 err:' + err);
    console.log(p4);
});
以上是执行了resolve()的Promise,他们都去执行了回调函数then()。接下来我们看看reject()的例子
const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('reject');
        resolve('resolve');
    });
}).then((res) => {
    console.log('p1 res:' + res);
    console.log(p1);
}).catch((err) => {
    console.log('p1 err:' + err);
    console.log(p1);
});
const p2 = new Promise((resolve, reject) => {
    throw('error');
}).then((res) => {
    console.log('p2 res:' + res);
    console.log(p2);
}).catch((err) => {
    console.log('p2 err:' + err);
    console.log(p2);
});
const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        throw('error');
    });
}).then((res) => {
    console.log('p3 res:' + res);
    console.log(p3);
}).catch((err) => {
    console.log('p3 err:' + err);
    console.log(p3);
});
setTimeout(() => {
    console.log(p3);
});
通过执行代码发现,p1 p2被执行了catch回调,原因就是Promise主动执行了reject()或者在同步代码里面抛出了异常。但是我们发现p3没有执行catch回调,并且打印出来的Promise p3状态为pending,说明Promise的异步代码里面抛出的异常不会被catch到。
接下来我们再来看看catch调用的调用:
const p1 = new Promise((resolve, reject) => {
    throw('error');
}).then((res) => {
    console.log('p1 res:' + res);
    console.log(p1);
}).catch((err) => {
    console.log('p1 err:' + err);
    console.log(p1);
    return 100;
}).then((res) => {
    console.log('p1 res1:' + res);
    console.log(p1);
});
const p2 = new Promise((resolve, reject) => {
    throw('error');
}).then((res) => {
    console.log('p2 res:' + res);
    console.log(p2);
}).catch((err) => {
    console.log('p2 err:' + err);
    console.log(p2);
    throw('error');
}).then((res) => {
    console.log('p2 res1:' + res);
    console.log(p2);
}).catch((err) => {
    console.log('p2 err1:' + err);
    console.log(p2);
});
这里有个小细节,就是在执行catch回调中,没有抛出异常,这时候返回的是一个状态是fulfilled的Promise,他会继续执行之后的then回调。
const p1 = new Promise(function(resolve, reject) {
    resolve();
    console.log('p1 resolve');
    throw('error');
    console.log('p1 error');
});
const p2 = new Promise(function(resolve, reject) {
    reject();
    console.log('p2 reject');
    throw('error');
    console.log('p2 error');
});
最后是抛出异常,在resolve()或reject()后面抛出的错误会被忽略,但是其他在抛出错误代码之上的代码还是会被执行。
下面是Promise的总结:
- 
执行了 resolve(),Promise状态会变成fulfilled,即 已完成状态
- 
执行了 reject(),Promise状态会变成rejected,即 被拒绝状态
- 
Promise只以 第一次为准,第一次成功就永久为fulfilled,第一次失败就永远状态为rejected
- 
Promise的同步代码中有 throw的话,就相当于执行了reject()
- 
Promise里没有执行 resolve()、reject()以及throw的话,这个promise的状态也是pending
- 
基于上一条, pending状态下的promise不会执行回调函数then()
- 
执行 catch回调中,没有抛出异常,这时候返回的是一个状态是fulfilled的Promise
- 
Promise的异步代码中有 throw的话,Promise无法捕获,只能通过用try...catch包裹Promise解决
- 
基于上一条, pending状态下的promise不会执行回调函数then()
- 
在 resolve()或reject()后面抛出的错误会被忽略
async/await
async 函数是使用async关键字声明的函数。async 函数是AsyncFunction构造函数的实例,并且其中允许使用await关键字。async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用Promise。
下面是async的基础用法:
async function async1 () {
    console.log('async1 000');
    let await1 = await 1000;
    console.log('await1:' + await1);
    console. log('async1 100');
    let await2 = await Promise.resolve(2000);
    console.log('await2:' + await2);
    console. log('async1 200');
    let await3 = await p1;
    console.log('await3:' + await3);
    console. log('async1 300');
    let await4 = await async2();
    console.log('await4:' + await4);
    console. log('async1 400');
}
const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('resolve');
    });
});
async function async2() {
    console. log('async2 000');
    return 400;
}
async1();
可以看到上面async1函数里的代码以同步的形式编写,但是具有异步行为。
如果await的异步代码出现异常则可以使用try/catch代码块捕获:
async function async2 () {
    try {
        let await1 = await p2;
    } catch (err){
        console.log('catch async2');  //catch async2
    }
}
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('resolve');
    });
});
async2();
- 
async函数一定会返回一个Promise对象
- 
如果一个 async函数的返回值看起来不是Promise,那么它将会被隐式地包装在一个Promise中
- 
async函数可能包含0个或者多个await表达式
- 
await表达式会暂停整个async函数的执行进程并出让其控制权,只有当其等待的基于Promise的异步操作被兑现或被拒绝之后才会恢复进程
- 
Promise的解决值会被当作该await表达式的返回值
- 
使用 async/await关键字就可以在异步代码中使用普通的try/catch代码块
以一道面试题结束
const async1 = async () => {
    console.log('async1');
    setTimeout(() => {
        console.log('timer1');
    }, 2000);
    await new Promise(resolve => {
        console.log('promise1');
    })
    console.log('async1 end');
    return 'async1 success';
} 
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
    .then(res => {
        console.log(res);
        return 5
    })
    .then(Promise.resolve(3))
    .catch(4)
    .then(res => console.log(res))
setTimeout(() => {
    console.log('timer2');
}, 1000);
点击查看答案
    'script start'
    'async1'
    'promise1'
    'script end'
    1
    5
    'timer2'
    'timer1'
 
解析:
- 
第一步代码开始,先声明了一个函数 async,还未执行,接着继续执行代码,遇到了script start,将其输出到控制台
- 
执行 async1,程序跳去async里面,遇到了async1,将其输出到控制台
- 
接着是 setTimeout,这时候开启一个计时器,2秒后会将setTimeout的回调放去宏任务执行
- 
遇到关键字 await,await的是一个Promise,直接执行Promise,将promise1输出到控制台
- 
接下来要留意下,代码里的 Promise是没有resolve()或reject()的,就是说await还一直在等待Promise
- 
到这一步, async1里面的代码执行完了,接着执行剩下的代码,于是遇到了script end,将其输出到控制台
- 
然后又遇到一个 Promise.resolve(1),接着把.then()放去微任务里面等待执行
- 
接下来要回到 setTimeout,1秒后会将setTimeout的回调放去宏任务执行
- 
这时候主线程的代码已经执行完了,就开始取出微任务里面的任务执行,即执行 .then(),而.then()所在的Promise已经resolve,并且返回了1,所以.then的res是1,将其输出到控制台
- 
同理继续执行 Promise.resolve(3)所在的.then,又因为这个then没有return,所以将其上级的参数透传下去,故最后执行的.then的res是5,将其输出到控制台
最后,让我们一起加油吧!
参考资料: