异步编程简述:
- 无论是在浏览器环境中还是在node环境中,我们都会使用JavaScript
- 完成各种异步操作,如在浏览器环境中的定时器、事件、ajax等或是node环
- 境中的文件读取、事件等。伴随着异步编程的就是回调机制(复习jQuery)。
- 明确一点异步编程避免不了回调。
let obj = {};let fs = require('fs');fs.readFile('./data/name.txt', 'utf-8', (err, data) => { obj.name = data; Store.fire(obj);});fs.readFile('./data/number.txt', 'utf-8', (err, data) => { obj.number = data; Store.fire(obj);});fs.readFile('./data/score.txt', 'utf-8', (err, data) => { obj.score = data; Store.fire(obj);});let Store = { list: [], times: 3, subscribe (func) { this.list.push(func); }, fire (...args) { --this.times == 0 && this.list.forEach(ele=>{ ele.apply(null, args); }) }}Store.subscribe(show);function show (data) { console.log(data);}复制代码
异步编程问题:
- 产生回调地狱,难于维护和扩展。
- try catch只能捕获同步代码中出现的异常。
- 同步并发的异步存在一定的问题。
解决方案:
- ES6 Promise可以解决回调地狱、以及同步并发的异步问题。(异步操作
- 的异常捕获有其他方式解决。)
- 但依旧会有明显的回调痕迹,之后学习ES6的generator 、ES7的
- async await争取让异步代码看起来和同步一样。写起来更优雅一些。
Promise(.then)
微任务和宏任务
console.log('script start');setTimeout(function() { console.log('setTimeout');}, 0);Promise.resolve().then(function() { console.log('promise1');}).then(function() { console.log('promise2');});console.log('script end');复制代码
打印顺序是什么?
正确答案是: ==script start, script end, promise1, promise2, setTimeout==
- 每个线程都会有它自己的event loop(事件循环),所以都能独立运行。然而所有同源窗口会共享一个event loop以同步通信。event loop会一直运行,来执行进入队列的宏任务。一个event loop有多种的宏任务源(译者注:event等等),这些宏任务源保证了在本任务源内的顺序。但是浏览器每次都会选择一个源中的一个宏任务去执行。这保证了浏览器给与一些宏任务(如用户输入)以更高的优先级。好的,跟着我继续……
宏任务(task)
- 浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task->渲染->task->...)
- 鼠标点击会触发一个事件回调,需要执行一个宏任务,然后解析HTMl。还有下面这个例子,setTimeout
- setTimeout的作用是等待给定的时间后为它的回调产生一个新的宏任务。这就是为什么打印‘setTimeout’在‘script end’之后。因为打印‘script end’是第一个宏任务里面的事情,而‘setTimeout’是另一个独立的任务里面打印的。
微任务(Microtasks )
-
微任务通常来说就是需要在当前 task 执行结束后立即执行的任务,比如对一系列动作做出反馈,或或者是需要异步的执行任务而又不需要分配一个新的 task,这样便可以减小一点性能的开销。只要执行栈中没有其他的js代码正在执行且每个宏任务执行完,微任务队列会立即执行。如果在微任务执行期间微任务队列加入了新的微任务,会将新的微任务加入队列尾部,之后也会被执行。微任务包括了mutation observe的回调还有接下来的例子promise的回调。
-
一旦一个pormise有了结果,或者早已有了结果(有了结果是指这个promise到了fulfilled或rejected状态),他就会为它的回调产生一个微任务,这就保证了回调异步的执行即使这个promise早已有了结果。所以对一个已经有了结果的promise调用.then(yey, nay)会立即产生一个微任务。这就是为什么‘promise1’,'promise2'会打印在‘script end’之后,因为所有微任务执行的时候,当前执行栈的代码必须已经执行完毕。‘promise1’,'promise2'会打印在‘setTimeout’之前是因为==所有微任务总会在下一个宏任务之前全部执行完毕。==
.then的链式操作
- 上一个不抛出错误的话, 那下一个then会默认执行成功回调函数, 抛出错误的话会执行失败回调函数
- 上一个then的返回至会作为下一个then注册函数的执行参数
- 上一个then返回一个Promise对象的话,后面的then就会成为这个对象的注册函数
// resolve 和 resolve是互斥事件let oP = new Promise((resolve, reject)=> { // 在构造函数中同步执行 console.log('123'); reject('aaa'); resolve('ddd'); });oP.then((data)=> { // 被当作微任务 // 异步执行 // setTimeout 是宏任务 // 宏任务 微任务 // task queue 1 task queue 2 // 微任务有优先执行权 console.log(data); return 20;}, (reason)=>{ console.log(reason); return 30;}).then((data)=> { console.log('ok then2 ' + data);}, (reason)=> { console.log('no then2 ' + reason);});// .then的链式操作// 上一个不抛出错误的话, 那下一个then会默认执行成功回调函数, 抛出错误的话会执行失败回调函数// 上一个then的返回至会作为下一个then注册函数的执行参数// 上一个then返回一个Promise对象的话,后面的then就会成为这个对象的注册函数 复制代码
用解决回调地狱
回调地狱
// name.txt: ./data/number.txt// number.txt: ./data/score.txt// score.txt: 100let fs = require('fs');fs.readFile('./data/name.txt', 'utf-8', (err, data)=> { console.log(data); if(data) { fs.readFile(data, 'utf-8', (err, data)=> { console.log(data); if(data) { fs.readFile(data, 'utf-8', (err, data)=> { console.log(data); }); } }); }});复制代码
使用Promise后
// name.txt: ./data/number.txt// number.txt: ./data/score.txt// score.txt: 100let fs = require('fs');function readFile(path) { return new Promise((resolve, reject) => { fs.readFile(path, 'utf-8', (err, data) => { if (data) { resolve(data); } }); });}readFile('./data/name.txt').then((data) => { console.log(data); return readFile(data);}).then((data) => { console.log(data); return readFile(data);}).then((data) => { console.log(data);});// node运行结果 // ./data/number.txt// ./data/score.txt// 100复制代码
.then .catch .finally
// 链式操作当遇到空的.then时,就当它不存在let oP = new Promise((res, rej) => { res();});oP.then(() => { throw new Error('www');}).then(() => {}).catch((err)=>{ console.log(err); // throw new Error('dasf'); return 10;}).then((val)=>{ console.log('catch after ok:' + val); // 输出},(reason)=>{ console.log('catch after no:' + reason) // 解开catch报错注释的时候输出}).finally(()=>{ console.log('over'); });复制代码
Promise.all()获取并发进程的结果 Promise.race() 赛跑
-
Promise.all可以将多个Promise对象实例包装成一个新的Promise实例
-
同时,成功和失败的返回至使不同的,成功的时候返回的是一个结果数组失败的时候则返回最先被reject失败状态的值
// 在数组中的所有异步进程都成功执行后再回执行.then后的resolve 有一个进程不成功都只会执行rejectPromise.all([readFile('./data/number.txt'), readFile('./data/name.txt'), readFile('./data/score.txt')]).then((val) => { console.log(val);}, (reason)=> { console.log('no');})复制代码
- 顾名思义, Promise.race就是赛跑的意思,意思就是说,Promise.race([p1,p2,p3])里面那个结果获得的快,就返回那个结果,不管结果本身世成功状态还是失败状态
// race 赛跑 数组中的线程谁先成功 后面的then参数就是谁的返回值Promise.race([readFile('./data/number.txt'), readFile('./data/name.txt'), readFile('./data/score.txt')]).then((val) => { console.log(val);}, (reason)=> { console.log('no');})复制代码
MyPromise
执行
let oP = new MyPromise((res, rej) => { res('ee'); }); oP.then((val)=> { console.log(val,'ok') return new MyPromise((res, rej)=>{ res(0); }); },(reason)=>{ console.log(reason,'no'); return 12; }).then(val=> { console.log(val); });复制代码
MyPromise文件
function MyPromise(executor) { var self = this; self.status = 'pending'; // 状态 self.resolveValue = null; self.rejectReason = null; self.resolveCallBacksList = []; // 实现在执行函数中可以使用异步例如setTimeout self.rejectCallBacksList = []; // function resolve(value) { if (self.status === 'pending') { self.status = 'Fulfilled'; self.resolveValue = value; self.resolveCallBacksList.forEach((ele) => { ele(); }); } } function reject(reason) { if (self.status === 'pending') { self.status = 'Rejected'; self.rejectReason = reason; self.rejectCallBacksList.forEach((ele) => { ele(); }); } } // 报错时的处理方法 所有执行用 try catch 包裹 try { // 执行我们在外界传入的函数, 将对象里的resolve, reject函数传进去, 在new一个新的Promise时, 自己定义的名字只是用来接收 有点绕? executor(resolve, reject); } catch (e) { reject(e); }};// 如果then的返回值是对象时的处理方式function ResolutionReturnPromise(nextPromise, returnValue, res, rej) { if(returnValue instanceof MyPromise) { // Promise对象 returnValue.then(function (val) { res(val); }, function (reason) { rej(reason); }) }else { res(returnValue); }}MyPromise.prototype.then = function (onFulfilled, onRejected) { var self = this; // 判断注册函数是否为空, 如果是true就返回以下函数 将返回值作为下一个then的参数 if (!onFulfilled) { onFulfilled = function (val) { return val; } } if (!onRejected) { onRejected = function (reason) { return new Error(reason); } } // 链式操作? new一个新的Promise时,里面的代码是同步执行的 var nextPromise = new MyPromise(function (res, rej) { if (self.status === 'Fulfilled') { // 判断状态 下同 setTimeout(() => { try { var nextResolveValue = onFulfilled(self.resolveValue); // 执行注册函数并接收返回值 ResolutionReturnPromise(nextPromise, nextResolveValue, res, rej); } catch (e) { rej(e); } }, 0); } if (self.status === 'Rejected') { setTimeout(() => { try { var nextRejectReason = onRejected(self.rejectReason); ResolutionReturnPromise(nextPromise, nextRejectReason, res, rej); } catch (e) { rej(e); } }, 0); } if (self.status === 'pending') { if (onFulfilled) { self.resolveCallBacksList.push(function () { setTimeout(() => { try { var nextResolveValue = onFulfilled(self.resolveValue); ResolutionReturnPromise(nextPromise, nextResolveValue, res, rej); } catch (e) { rej(e); } }, 0); }); } if (onRejected) { self.rejectCallBacksList.push(function () { setTimeout(() => { try { var nextRejectReason = onRejected(self.rejectReason); ResolutionReturnPromise(nextPromise, nextRejectReason, res, rej); } catch (e) { rej(e); } }, 0); }); } } }); return nextPromise; // 返回一个Promise对象}MyPromise.all = function (promiseArr) { return new MyPromise(function (resolve, reject) { if(!Array.isArray(promiseArr)) { return reject(new TypeError("argument must be anarray")); } var promiseArrValue = []; promiseArr.forEach((promise, index) => { promise.then((val)=>{ promiseArrValue.push(val); if(index == promiseArr.length - 1) { return resolve(promiseArrValue); } }, (reason)=>{ reject(reason); }) }); });}复制代码