一、异步的开端
-
JS执行机制
JS是单线程的语言,所以会有代码阻塞,要想避免代码阻塞就得通过异步,而异步的实现原理就是通过事件环,它会把异步任务寄托到浏览器,浏览器将异步任务处理完后会将其推送到任务队列,等JS主线程执行完毕后,会一个一个执行任务队列里的异步任务。
-
异步存在的问题
-
回调地狱 —— 难于维护,不便拓展
呈现倒三角的写法就是回调地狱
let fs = require('fs'); fs.readFile('./name.txt','utf-8',(err,data)=>{ console.log(data); // ./number.txt if(data){ fs.readFile(data,'utf-8',(err,data)=>{ console.log(data); // score.txt fs.readFile(data,'utf-8',(err,data)=>{ console.log(data); // 90 }) }) } })
-
try-catch —— 难以捕获异常
只能捕获同步异常,无法捕获异步错误
直接报错
try { setTimeout(()=>{ console.log(a); // Uncaught ReferenceError: a is not defined },90) } catch (e) { console.log(e); }
捕获不到
let fs = require('fs'); try { fs.readFile('./nam.txt', 'utf-8', (err, data) => { if (err) { console.log(err); // [Error: ENOENT: no such file or directory, open 'C:\Users\21757\Desktop\ES6\nam.txt'] { // errno: -4058, // code: 'ENOENT', // syscall: 'open', // path: 'C:\\Users\\21757\\Desktop\\ES6\\nam.txt' // } } }); } catch (e) { console.log(e); }
-
同步并发异步任务 —— 没法判断谁先执行
每一个异步函数中都要加入 arr.length === 3 && show(arr) 判断是否读取完
let fs = require('fs'); let arr = [] function show(data){ console.log(data); } fs.readFile('./name.txt','utf-8',(err,data)=>{ if(data){ arr.push(data) } arr.length === 3 && show(arr); }) fs.readFile('./number.txt','utf-8',(err,data)=>{ if(data){ arr.push(data) } arr.length === 3 && show(arr); }) fs.readFile('./score.txt','utf-8',(err,data)=>{ if(data){ arr.push(data) } arr.length === 3 && show(arr); }) // ['./number.txt','scores.txt','90']
-
-
promise演变历史
-
Callbacks() —— 管理回调列表(已过时)
在jquery中可以通过 add 方法将异步任务推到 Callbacks 里面进行管理
// 引入JQuery let cb = $.Callbacks(); function a(x,y){ console.log('a',x,y) } function b(x,y){ console.log('b',x,y) } cb.add(a,b); //添加异步方法 cb.fire(10,20); //执行所有异步 //a 10 20 //b 10 20
-
deferred(即将淘汰)
底层是通过 Callbacks 管理当前的回调,实现异步的管理
缺点:可以显示调用 reject,于是采用 promise 来解决,这就是 promise 的由来
function waitHandle(){ var dtd = $.Deferred(); //创建一个 deferred 对象; var wait = function(dtd){ //传入一个 deferred 的对象 var task = function(){ console.log('执行完成'); dtd.resolve(); //表示异步任务已经完成 // dtd.reject(); //表示异步任务失败或出错;打印失败的回调 } setTimeout(task,2000); // return dtd; //里层函数,返回 deferred 对象 return dtd.promise(); //注意,这里返回的是 promise 而不是直接返回 deferred } // 外层函数返回 里层函数的执行结果 return wait(dtd) } var w = waitHandle(); console.log(w); //w.done(function(){ // console.log('ok'); //}).fail(function(){ // console.log('err'); //}); // 执行完成 ok // w.reject(); // deferred弊端:deferred 对象可以显示调用 reject 方法,而 promise 对象无法调用 w.then(function(){ console.log('ok'); }).fail(function(){ console.log('err'); }); // 执行完成 ok
-
promise A+规范
本身是社区的一个规范,用于定义 promise 的用法,最后被囊括在了es6
-
二、promise
-
promise初识
是一种异步操作,js内置的构造函数
参数是一个函数(executor 执行者)
console.log(new Promise(function(resolve,reject){})); // Promise {<pending>} // [[Prototype]]: Promise // catch: ƒ catch() // constructor: ƒ Promise() // finally: ƒ finally() // then: ƒ then() // Symbol(Symbol.toStringTag): "Promise" // [[Prototype]]: Object // [[PromiseState]]: "pending" // [[PromiseResult]]: undefined
-
同步执行
new Promise(function(resolve,reject){ console.log('promise'); }); console.log(1); // 'promise' 1
-
三种状态
- panding(进行中)
- fufilled(也叫resolve)(已成功)
- reject(已失效)
两个过程:padding - resolve、padding - reject,promise的resolve、reject代表的是这两个过程
-
两个特征
- 对象的状态不受外界影响(唯一能影响的就是当前它所代表的异步事件)
- 状态不可逆(promise固化以后,再对promise对象添加回调,是可以直接拿到这个结果的,如果说是事件的话,一旦错过了,就是真的错过了)
-
executor参数
通过调用resolve、reject方法,改变当前的异步操作的状态,并调用绑定回调函数
-
绑定回调函数
当 promise 的状态发生变化时调用
通过 then 方法绑定状态为成功或者失败的回调函数
参数:注册成功回调 / 注册失败回调
let promise = new Promise((resolve,reject)=>{ setInterval(function(){ Math.random()*100 >60 ? resolve('ok'):reject('no'); },30); }) promise.then((val)=>{ console.log(val); },(reason)=>{ console.log(reason); }) //随机的ok no
通过promise代表当前的异步任务,当异步任务完成以后调用相应的方法来改变promise当前的状态,并调用promise通过.then的方式绑定的回调函数
-
执行顺序
promise里面是同步执行
而resolve、reject是promise的异步操作,执行会改变当前promise的状态,调用绑定的回调函数
let promise = new Promise((resolve,reject)=>{ console.log(0); resolve('1'); // 异步操作,调用的回调属于异步任务 }) promise.then((val)=>{ console.log(val); },(reason)=>{ console.log(reason); }) console.log(2); // 先执行主线程任务 0 2 再执行任务队列当中的 1
当有两个异步任务时
setTimeout(function(){ console.log('SET TIME'); },30); let promise = new Promise((resolve,reject)=>{ console.log(0); resolve('1'); }); promise.then((val)=>{ console.log(val); },(reason)=>{ console.log(reason); }); console.log(2); //0 2 1 SET TIME
JS异步代码中,
分为宏任务(宏任务队列),
微任务(微任务队列):promise、process.nextTick() 优先级更高(除了这两个其它都是宏任务)
Promise.resolve().then(()=>{ //在executor里面调用是等效的 console.log('promise1'); setTimeout(()=>{ console.log('setTime2'); }) }) setTimeout(()=>{ console.log('setTime1'); Promise.resolve().then(()=>{ console.log('promise2'); }) }) //第一轮:promise1 setTime1 第二轮:promise2 setTime2
-
链式调用
then默认返回promise对象,所以可以链式调用
let promise = new Promise(function(resolve,reject){ setInterval(function(){ Math.random * 100 > 60 ? resolve('ok') : reject('no'); },30) }) promise.then((val)=>{ console.log(val) },(reason)=>{ console.log(reason) }).then((val)=>{ // 因为上一个默认promise没有传值,所以这里val是undefined console.log(val); },(reason)=>{ console.log(reason); }) //no undefined
第一次then的返回值作为下一次then的执行参数
let promise = new Promise(function(resolve,reject){ setInterval(function(){ Math.random() * 100 > 60 ? resolve('ok') : reject('no'); },30) }); promise.then((val)=>{ console.log(val) return 3; },(reason)=>{ console.log(reason) return 2; }).then((val)=>{ console.log(val); },(reason)=>{ console.log(reason); }); // ok 3 or no 2
如果第一次就return一个新的Promise 那么就会调用第二个then,参数就自动获取resolve中的了
let promise = new Promise(function(resolve,reject){ setInterval(function(){ Math.random() * 100 > 60 ? resolve('ok') : reject('no'); },30) }) promise.then((val)=>{ console.log(val) return new Promise((resolve,reject)=>{ resolve('newPromise') }) },(reason)=>{ console.log(reason) return new Promise((resolve,reject)=>{ reject('rejectPromise') }) }).then((val)=>{ console.log(val); },(reason)=>{ console.log(reason); }) //ok newPromise or no rejectPromise