JS中的Promise和async/await
前言
JS执行是单线程的,但是在JS中需要有大量进行查询、获取数据的操作,例如AJAX,如果都按照顺序执行,那么在用户体验等多个方面肯定是极差的。那么就衍生了一系列的异步操作。
callback
最简单,最早产生的异步解决方案就是
callback,常说的
回调函数,举个例子:
var A=function(){ // var aa= fs.readFileSync('test.txt',{encoding:'utf-8'}) // console.log(aa) for(var i=0;i<999;i++){} console.log('A') } var B=function(callback){ callback() console.log('B') } B(A)
执行后结果如下:
A
B
这个感觉不对劲,不是说
callback是异步吗,为什么还是顺序执行呢。其实callback函数仍然在主线程中执行,所以,调用callback,当前线程还是会去执行callback。网上有的解释并没有解释清楚。那
callback和
异步操作到底有什么关系呢?看下边个例子:
console.log('A') $('button').on('click',function(){ console.log('success') }) console.log('B')
执行结果如下:
A
B
为什么success没有被打印出来呢,应该学过
JS都知道把,因为它的打印是有条件的,需要点击的时候才能触发,总之就是可以通过这个条件来控制回调函数的执行顺序,现在可能有人还是会不太理解。
JS异步编程原理与回调函数
ES6之前,JavaScript中异步编程分为3类:DOM事件(如onclick)、网络请求(如ajax)、定时器(setTimeout/setInterval)。他们均使用回调函数来进行异步调用。
JS执行栈是单线程,但是JS的宿主环境不是单线程的(如浏览器、Node),浏览器内部是允许多个线程同时运行的,除JavaScript引擎线程外,还有事件触发线程、HTTP请求线程、定时器触发线程,他们和JavaScript线程是互不影响的,不会造成阻塞,JS执行的线程(即JavaScript引擎的所在线程)为主线程,浏览器会执行一个类似于while的轮询过程,每次循环查看线程中是否有待处理的任务,(如浏览器点击事件、AJAX请求、定时器等),如果存在则把该任务添加到JS主线程的执行任务列表中等待执行。
那么我们就很好明白了,诸如事件请求、定时器、事件点击等确实是异步的,但是不是由于
JavaScript本身的作用,
JavaScript本身依旧是单线程的,当碰到请求等事件时,浏览器会新开一个线程,当回调函数
callback调用的时候,那么之前的异步事件就产生了变更把回调函数放到
JavaScript引擎任务队列中等待执行。
尽管回调函数是一种异步解决方案,但正是异步操作的问题,会产生回调地狱的问题,如下
function async(){ setTimeout(function(){ //回调函数1 console.log(1); setTimeout(function(){ //回调函数2 console.log(2); setTimeout(function(){ //回调函数2 console.log(3); },1000); },1000); },1000) } async(); //调用结果:1s后打印1 2s后打印2 3s后打印3
这是网上随便找的一个例子,我们可以看到一大堆的 {} 、(),看完脑袋有点疼,一旦嵌套层级变多,代码结构就变得很不乐观。
Promise
promise是
ES6中提出的一种异步解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,
ES6将其写进了语言标准,统一了用法,原生提供了
Promise对象。其只有then这一个方法,
then方法带有两个参数:
- 成功回调
- 失败回调
promise对象有三个状态:
- pending(进行中,未完成)
- resolved(已完成)
- rejected(已失败)
(1)我们将上边的callback回调函数包装成
Promise
function async1(data){ console.log(data) var p=new Promise(function(resolve,reject){ setTimeout(function(){ resolve(2) },1000) }) return p } function async2(data){ console.log(data) var p=new Promise(function(resolve,reject){ setTimeout(function(){ resolve(3) },1000) }) return p }
(2)使用
then链式调用这三个方法
async() .then(function(data){ return async1(data) }) .then(function(data){ return async2(data) }) .then(function(data){ console.log(data) })
(3)还可以简化为
async() .then((data)=> async1(data)) .then((data)=> async2(data)) .then((data)=>console.log(data)) //调用结果:1s后打印1 2s后打印2 3s后打印3
我们可以看到异步操作的结构清晰了许多,当然我们可以通过
rejected对异步操作进行错误,我们对函数进行错误处理。
function async(){ var p=new Promise(function(resolve,reject){ setTimeout(function(){ resolve(1) },1000) }) return p } function async1(data){ console.log(data) var p=new Promise(function(resolve,reject){ setTimeout(function(){ reject(2) },1000) }) return p } function async2(data){ console.log(data) var p=new Promise(function(resolve,reject){ setTimeout(function(){ resolve(3) },1000) }) return p }
执行结果
1 (node:17952) UnhandledPromiseRejectionWarning: 2 (node:17952) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1) (node:17952) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
报错了,大概意思就是没对错误进行处理,使用
catch方法捕捉错误。
function async(){ var p=new Promise(function(resolve,reject){ setTimeout(function(){ reject('这是错误1') },1000) }) return p } function async1(data){ console.log('这是正确回调') } async() .then(async1,(e)=>{console.log(e)}) //打印结果 这是错误1
我们还可以用
catch方法来处理rejected回调
async() .then(async1) .catch((data)=>console.log(data))
catch方法的另一个作用是如果执行
resolve时代码出错(抛出错误),不会卡死出错,会进到
catch函数里边
await和async
await字面意思就是
等待,在
JavaScript中意思和这个差不多,
async是异步操作,而
await就是
等待一个异步任务完成的结果。简单的说
await是一个操作符,
async是异步的方法。
await只能用在async中
async function demo(){ const data1= await async() const data2=await async1() console.log(data1,data2) } demo() //打印结果 1 2
可以看到结果1和结果2同时打印,那么
await操作符的作用就比较明显了,
await就是将异步
Promise同步化的一种解决方案。不需要在
then链中进行回调操作。
总结
JavaScript
本身仍然是单线程的,没有异步操作一说,但是其宿主环境
是多线程的问题,造成可以进行一些异步操作。callback
本身也不是异步操作的但是结合一些浏览器事件、HTTP请求等造成了callback回调异步操作的问题。callback
回调函数会造成地狱回调的问题,代码结构不清晰,Promise
对象很好的解决课这个问题。await
是操作符,是async同步化的一种方法。
- angular2 学习笔记 ( Rxjs, Promise, Async/Await 的区别 )
- 前端异步流程工具的方案promise、gengerator、async-await、node.js的nextTick setlmmidate、第三方库(async.js)
- js的async/await与普通的promise和ajax对比
- js Promise/async/await
- js异步回调Async/Await与Promise区别
- 重构:从Promise到Async/Await
- 前端的异步解决方案之Promise和Await-Async
- Node.js回调黑洞全解:Async、Promise 和 Generator
- async/await与promise(nodejs中的异步操作问题)
- 高级之路篇十八:setTimeout、Promise、async、await
- async/await基本理解及项目案例(结合Promise)
- Promise和async/await
- Promise原理讲解 async+await应用(异步回调解决方案)
- Promise、async/await的用法
- Callback Promise Generator Async-Await 和异常处理的演进
- Node.js如何对SQLite的async/await封装详解
- 解决js异步问题的方法--async和await(ES7)
- 重构:从Promise到Async/Await
- Callback Promise Generator Async-Await 和异常处理的演进
- Callback Promise Generator Async-Await 和异常处理的演进_2