promise和async/await的区别
两者都是做异步处理的, 使异步转为同步,目的都是为了解决异步回调产生的“回调地狱”。
同步: 顺序执行,始终和前文保持在一个上下文,可以快速捕获及处理异常。由于js是单线程,当代码量多时容易造成阻塞,耗费时间。
异步: 由浏览器(多线程)提供,解决阻塞,异步任务始终在同步任务全部执行完毕后才执行,像回调函数,ajax、setTimeout。提高了执行效率,节省时间,但是会占用更多资源,不利于对进程控制。
btw… 异步任务其实是可以和同步任务同时进行的,但是异步任务的回调函数(真正的异步)一定是在同步任务全部执行完毕后执行的,因为异步回调任务一定是在js线程中完成的。即异步操作在浏览器线程,而异步执行回到js线程。
回调地狱: 当许多功能连续调用,回调函数层层嵌套,形成一个三角形,造成可读性差,代码可维护性可迭代性差,可扩展性差。
一、什么是promise(承诺)
Promise是ES6中的一个内置对象,(实际是一个构造函数,通过函数可以创建一个promise对象),作用为解决异步调用问题
特点:
①、三种状态:pending(进行中)、resolved(已完成)、rejected(已失败)。只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都不能改变这个状态。
②、两种状态的转化:其一,从pending(进行中)到resolved(已完成)。其二,从pending(进行中)到rejected(已失败)。只有这两种形式的转变。
③、缺点:在pending阶段,无法取消状态,且无法判断pending的进度(是刚开始还是即将结束)
二、使用promise
若需要使用带setTimeout()。其在Chrome中可以传多个参数
setTimeout(function (a, b) { console.log(a + b); }, 1000, 20, 50); // 70
创建一个promise实例:
var mypromise = new Promise( ( resolve,reject )=>{ //构造函数new创建对象,函数必须有 // do something... If( /*异步操作成功*/ ){ resolve(value); // pending---resolve }else{ reject(error); //pending---reject } } ) // 后续操作:执行回调函数.then()方法来操作成功或失败要操作的事情 mypromise .then( (value)=>{ // success }, (error)=>{ // failure } )
箭头函数只是初始化promise对象,并且接受了resolve和reject两个函数作为参数。注意if语句不是必要的,只是要表达若异步成功,就调用resolve函数,状态转化,并将value值传递下去,因为promise还有一个.then()方法,即成功(或失败)后的一些操作,这也是resolve和reject内置函数存在的意义。反之若异步失败亦是。
举例: 使用构造函数创建对象
let p = new Promise(function(resolve, reject) { // Promise() 就是一个构造函数,通过new创建了p对象实例 console.log('i am promise'); var a = 2,b = 1; if(a>b){ resolve(a); }else{ reject(error) } }); p.then(function(value) { // 执行异步回调函数 console.log('resolved.'); },function(error){ console.log('reject') }); console.log('Hi!'); // i am promise; Hi; resolved; //可以看出promise是异步的
三、promise.prototype.then()
then()作用:是为promise实例添加状态发生改变时的回调函数。第一个参数(必选)是resolve状态的回调,第二个参数(可选)是reject状态回调的函数
then()方法是promise原型上定义的方法。输出promise.prototype可看到内置对象
then()方法支持链式调用,即上一个方法调用后的结果会传给下一个方法。
等同于:
mypromise.then(function(data) { console.log('Contents: ' + data); }).then( function(error) { console.error('出错了', error); } )
第一个回调函数完成后,会将结果作为参数,传递到下一个回调函数。
四、promise.prototype.catch()
建议then()不要使用第二个参数,而使用catch()
Promise.then( function(data){ // 建议 //success } ).catch( function(err){ //error } )
因为catch() 方法返回的还是一个promise对象,后续可以继续使用then()进行链式调用。
resolve — 对应then。只有调用了resolve才会触发then方法的回调
reject — 对应catch。只有调用了reject才会触发catch方法的回调
五、promise.all()
作用:将多个promise实例包装成一个新的promise实例
Var arr = [1,2,3]
Var p = promise.all(arr).then(…)
其中数组中1,2,3都是promise对象实例。若不是,就会先调用下面讲到的promise.resolve()方法,将参数先转为实例,再下一步处理。
p的状态(成功或失败再执行then回调) 由数组的所有实例对象决定:
1、只有所有实例对象都是resolve,p的状态才resolve。然后返回值也是数组,传递给p的回调函数;
2、当有一个实例被reject了,p状态就会是reject。此时第一个被reject的实例返回值,会传递给p的回调函数。
举例:
var p= [1,2,3,4,5,6].map( function(i){ console.log(i); } ) promise.all(p).then(...).catch(...)
通过数组map生成6个实例对象,然后作为参数传递promise.all(),只有当全部为resolve时才会调用then()方法,否则调用catch()方法。
六、promise.race()
同五。也是将多个promise实例包装成一个新的promise实例。状态亦是。
Var p = promise.race( [1,2,3] )
七、promise.resolve()
将现有的对象转化为promise对象。看参数的不同分4种情况…
八、promise.reject()
promise.reject(reason)方法也会返回一个新的promise实例,该实例状态为reject。
九、理解promise
其作用就是如果前面的代码非常耗时,就会阻塞后面要执行的代码,所以有异步操作。
举例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>testsettimeout</title> </head> <body> <div class="wrap"></div> <script> let promise = new Promise(function(resolve, reject) { console.log('Promise! 我是Promise对象里的函数,我最早出现是因为我要给后面一个耗时的家伙提供数据a'); var a = 20; resolve(a); }); promise.then(function(value) { console.log('哈哈哈,我得到数据a了,要开始执行喽。我是一个非常耗时的操作,依赖前面的数据,但是我想快一点执行,这有利于用户体验,所以别把我放在后头啊;我被放在了一个新的线程上,不会阻塞别人的'); for (var i = 0; i < 1000000; i++) { var li = document.createElement("li"); document.querySelector(".wrap").appendChild(li); } console.log("执行完毕"); }); console.log('Hi! 我是什么都不依赖的程序,但我也想快一点执行,不能委屈我啊'); </script> </body> </html> // 'Promise! 我是Promise对象里的函数... // Hi! 我是什么都不依赖的程序... // 哈哈哈,我得到数据a了,要开始执行喽... // 执行完毕
在new promise里的函数是立即执行函数,执行顺序:
(匿名函数—Promise!…)-----(若还有同步任务 —Hi! …)—紧接着是promise.then回调函数的执行。所以promise.then()函数才是真正的异步执行。
Async await
Async搭配await是ES7提出的,基于promise实现,也是非阻塞的异步转同步,让代码更加语义化,更有可读性。
Await函数不能单独使用(无效),事实上async函数会返回一个promise对象,可以使用then函数添加回调。执行函数时,一旦遇到await函数就会先返回一个promise对象,等到异步操作完成,再去执行后面的语句,若await异步操作出错,就相当于async返回的promise对象被reject了。
async函数在function前面有个async作为标识,意思就是异步函数,里面有个await搭配使用,await是等待的意思,那么它等待什么呢,它后面跟着什么呢?其实它后面可以放任何表达式,不过我们更多的是放一个返回promise 对象的表达式,它等待的是promise 对象的执行完毕,并返回结果。每到await的地方就是程序需要等待执行后面的程序,语义化很强,下面总结一下async函数的特点:
1. 语义化强
2. 里面的await只能在async函数中使用
3. await后面的语句可以是promise对象、数字、字符串等
4. async函数返回的是一个Promsie对象
5. await语句后的Promise对象变成reject状态时,那么整个async函数会中断,后面的程序不会继续执行
基于上面的async的特点,我们会用到异常捕获机制-----try…catch…
举例:Async搭配await发送异步请求
现在写一个函数,让它返回promise 对象,该函数的作用是2s 之后让数值乘以2
// 2s 之后返回双倍的值 function doubleAfter2seconds(num) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(2 * num) }, 2000); } ) } // 再写一个async 函数,从而可以使用await 关键字, await 后面放置的就是返回promise对象的一个表达式,所以它后面可以写上 doubleAfter2seconds 函数的调用 async function testResult() { let first = await doubleAfter2seconds(30); let second = await doubleAfter2seconds(50); let third = await doubleAfter2seconds(30); console.log(first + second + third); // 220 } testResult(); // 调用 console.log('先执行');
6秒后,控制台输出220, 我们可以看到,写异步代码就像写同步代码一样了,再也没有回调地狱了。
- 代码执行过程:调用testResult函数,然后再里面遇到了await函数,要等待,代码就执行到这,等后面的promise对象执行完毕,拿到promise resolve的值进行返回,然后await才会继续向下执行。
- 具体到 我们的代码, 遇到await 之后,代码就暂停执行了, 等待doubleAfter2seconds(30) 执行完毕,doubleAfter2seconds(30) 返回的promise 开始执行,2秒 之后,promise resolve 了, 并返回了值为60, 这时await 才拿到返回值60, 然后赋值给result, 暂停结束,代码继续执行,执行 console.log语句。
- Async/await相对来说更简洁,不用写很多的then(),不用匿名函数处理。
让try/catch可以同时处理同步和异步处理。在promise中就无法处理,要使用.catch()方法,代码冗余。 - 这里强调一下等待,当js引擎在等待promise resolve 的时候,它并没有真正的暂停工作,它可以处理其它的一些事情,如果我们在testResult函数的调用后面,console.log 一下,你发现 后面console.log的代码先执行。