好久不更文了,说明什么(呲牙笑)???说明我正躲在草丛里憋大招,哈哈,开玩笑,说明我最近真的很忙(落寞脸)。。今天主要说js中的异步操作。
ES6诞生以前,异步编程的方法,大概有下面四种
1、回调函数
2、事件监听
3、发布/订阅
4、Promise对象
5、es6中出现了Generator函数
6、es7中出现了async/await
今天主要简单介绍6种异步操作的使用。
所谓"异步",简单说就是一个任务分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。
回调函数
回调是异步编程中最基础的方法
var func1=function(callback){ console.log(1); callback(); } var func2=function(){ console.log(2); } func1(func2);
回调函数中最常见的形式就是ajax
事件监听
js中可以通过DOM绑定事件,实现异步事件监听
var elem = document.querySelector('#div'); var event = document.createEvent('Event'); // 定义事件名称myEvent event.initEvent('myEvent', true, true); // 监听myEvent elem.addEventListener('myEvent', function (e) { console.log('触发事件'); console.log(e); }, false); // 使用目标对象去派发事件,可以是元素节点/事件对象 elem.dispatchEvent(event);
发布订阅
发布—订阅模式又叫观察者模式,它定义对象间的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知
var Observer = (() => { // 为防止消息队列暴露而被篡改故将消息容器作为静态私有变量保存 var _message = {}; return { // 注册信息接口 regist:function(type,fn){ // 如果此消息不存在则应该创建一个该消息类型 if(typeof _message[type] === 'undefined'){ // 将动作推入到该消息对应的动作执行队列中 _message[type] = [fn]; // 如果此消息存在 }else{ // 将动作方法推入该消息嘴硬的动作执行序列中 _message[type].push(fn) } }, // 发布信息接口 fire:function(type,args){ // 如果该消息没有被注册,则返回 if(!_message[type]){ return } // 定义消息信息 var events = { type:type, // 消息类型 args:args || {} // 消息携带数据 }, i = 0; // 消息动作循环变量 len = _message[type].length // 消息动作长度 // 遍历消息东旭哦 for(; i <len; i++){ // 依次执行注册的消息对应的动作序列 _message[type][i].call(this,events); } }, // 移除信息接口 remove:function(type,fn){ // 如果消息动作队列存在 if(_message[type] instanceof Array){ // 从最后一个消息动作遍历 var i = _message[type].length -1; for(; i >= 0; i--){ // 如果存在该动作则在消息动作序列中移除相应动作 _message[type][i] === fn && _message[type].splice(i,1); } } } } })() Observer.regist('test',function(e){ console.log(e.type,e.args.msg); }) Observer.fire('test',{msg:'传递参数'});
Promise
所谓 Promise,就是一个对象,用来传递异步操作的消息
对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和 Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。
var promise = new Promise(function(resolve, reject) { if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } }); promise.then(function(value){ console.log(value) }).then(function(value){ console.log('1') }).then(function(value){ console.log('2') }).catch(function(e){ console.log(e) })
常用方法:
1、Promise.all()方法接受一个数组作为参数,用于将多个promise实例,包装成一个新的Promise实例
2、Promise.race()同样是将多个Promise实例,包装成一个新的Promise实例,与Promise.all()不同的是,Promise.race([p1,p2,p3])
只要有一个实例状态为resolved,那么Promise.race()的方法就会被触发。
3、Promise.resolve() 将现有对象转为promise对象,比如,bjquery的ajax返回的是deferred对象,通过promise的resolve()方法将其转换为promise对象。var jsPromise = Promise.resolve($.ajax('/whatever.json'));
4、Promise.reject()返回一个新的promise实例,状态为reject
Generator函数
function* gen(x){ var y = yield x + 2; return y; }var g = gen(1); g.next() // { value: 3, done: false } g.next() // { value: undefined, done: true }
调用generator函数返回的是内部的指针对象,调用next方法就会移动内部指针。Generator函数之所以能被用来处理异步操作,因为它可以暂停执行和恢复执行、函数体内外的数据交换和错误处理机制。
所以使用起来我们常常需要额外需要写一个自动执行generator函数的执行器函数,例如:
var fs = require('fs');var readFile = function (fileName){ return new Promise(function (resolve, reject){ fs.readFile(fileName, function(error, data){ if (error) return reject(error); resolve(data); }); }); };var gen = function* (){ var f1 = yield readFile('/etc/fstab'); var f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };// 自动执行器function run(gen){ var g = gen(); function next(data){ var result = g.next(data); if (result.done) return result.value; result.value.then(function(data){ next(data); }); } next(); } run(gen);
这么操作的话无疑给我们添加了很多麻烦,所以es7给我提供了async/await,用来规避generator的一些不便之处。
async/await
async函数基于Generator又做了几点改进:
1、内置执行器,将Generator函数和自动执行器进一步包装。
2、语义更清楚,async表示函数中有异步操作,await表示等待着紧跟在后边的表达式的结果。
3、适用性更广泛,await后面可以跟promise对象和原始类型的值(Generator中不支持)
它基于Promise使用async/await来优化then链的调用,其实也是Generator函数的语法糖。 async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来
await得到的就是返回值,其内部已经执行promise中resolve方法,然后将结果返回。使用async/await的方式重写前面的回调任务:
async function asyncAwaitFn(str) { return await new Promise((resolve, reject) => { setTimeout(() => { resolve(str) }, 1000); }) } const serialFn = async () => { //串行执行 console.time('serialFn') console.log(await asyncAwaitFn('string 1')); console.log(await asyncAwaitFn('string 2')); console.timeEnd('serialFn') } serialFn();
整理不易,一步一个坑,且行且珍惜。
作者:范小饭_
链接:https://www.jianshu.com/p/d49697a630c5