JavaScript | 异步处理
栏目: JavaScript · 发布时间: 5年前
内容简介:这种不连续的执行,就叫做异步。相应地,连续的执行,就叫做同步。函数作为一等公民,可以作为参数和返回值,也可以作为函数的参数函数可以作为返回值
- 所谓"异步",简单说就是一个任务分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段,比如,有一个任务是读取文件进行处理,异步的执行过程就是下面这样。
这种不连续的执行,就叫做异步。相应地,连续的执行,就叫做同步。
2 高阶函数
函数作为一等公民,可以作为参数和返回值,也可以作为函数的参数
2.1 可以用于批量生成函数
// 判断一个参数是否是字符串 function isString(param){ return Object.prototype.toString.call(param) == '[object String]'; } isString(123); // 判断一个参数是否是数组 function isArray(param){ return Object.prototype.toString.call(param) == '[object Array]'; } isArray([]); 复制代码
函数可以作为返回值
function isType(type){ return function(param){ return Object.prototype.toString.call(param) == `[object ${type}]`; } } let isString = isType('String'); let isArray = isType('Array'); console.log(isString({})) console.log(isArray([])) 复制代码
2.2 可以用于需要调用多次才执行的函数
函数可以作为参数传到另外一个函数里面
function eat(){ console.log("吃完了") } // 让他执行几次才会执行 function after(times,fn){ let count = 0; return function(){ if(count++==times){ fn(); } } } let newEat = after(3,eat); newEat(); newEat(); newEat(); 复制代码
3. 异步编程的语法目标,就是怎样让它更像同步编程,有以下几种
- 回调函数实现
- 事件监听
- 发布订阅
- Promise/A+ 和生成器函数
- async/await
4. 回调
比如我现在要读取一个文件,异步读取
let fs = require('fs'); fs.readFile('./1.txt','utf8',function(err,data){ if(err){ // 如果err有值,就表示程序出错 console.log(err); }else{ // 如果err为空就表示成功没有错误 console.log(data); } }); 复制代码
回调函数的问题
- 无法捕捉错误 try catch return
- 不能return
- 回调地狱
function read(filename){ fs.readFile(filename,'utf8',function(err,data){ if(err){ // 如果err有值,就表示程序出错 console.log(err); }else{ // 如果err为空就表示成功没有错误 console.log(data); } }); } try{ read('1.txt'); }catch(e){ console.log(e); } console.log(2); 复制代码
当你访问服务器的时候,比如请求一个html页面,比如用户列表。服务器一方面会去读取模板文件,可能是ejs、pug、jade、handlebar、另外一方面还要读取数据(可能会放在文件里,也可以会放在数据里),它们都很慢,所以都是异步的.
- 这种写法很难看
- 非常难以维护
- 效率比较低,因为它是串行的
fs.readFile('./template.txt','utf8',function(err,template){ fs.readFile('./data.txt','utf8',function(err,data){ console.log(template+''+data); }) }) 复制代码
如何解决这个回调嵌套的问题
6. 异步流程解决方案
6.1 通过事件发布订阅来实现
// 这node核心模块中一个类,通过它可以穿件时间发射器的实例,里面有两个核心方法 // 一个叫on emit,on表示注册监听,emit表示发射事件 let EventEmitter = require('events'); let eve = new EventEmitter(); // 这个html对象是存放 let html = {}; // template data // 监听数据获取成功事件,当事件发生之后调用回调函数 eve.on('ready',function(key,value){ html[key] = value; if(Object.key(html).lenght==2){ console.log(html); } }) fs.readFile('./template.txt','utf8',function(err,template){ // 1事件名 2参数往后是传递给回调函数的参数 eve.emit('ready','template',template); }) fs.readFile('./data.txt','utf8',function(err,data){ // eve.emit('ready','data',data); }) 复制代码
6.2 哨兵变量
// 通过一个哨兵来解决 let html = {} function done(key,value){ html[key]=value; if(Object.keys(html).length===2){ console.log(html); } }; fs.readFile('./template.txt','utf8',function(err,template){ done("template",data) }) fs.readFile('./data.txt','utf8',function(err,data){ done("data",data) }) 复制代码
// 通过一个哨兵来解决 let html = {} function render(lenght,cb){ let html = {}; if(Object.keys(html).length===lenght){ cb(html); } } let done = render(2,function(html){ console.log(html); }); fs.readFile('./template.txt','utf8',function(err,template){ done("template",data) }) fs.readFile('./data.txt','utf8',function(err,data){ done("data",data) }) 复制代码
6.4 生成器Generators/ yield
生成器是一个函数,可以用了生成迭代器
生成器函数和普通函数不一样,普通函数一旦调用一定会执行完
但是生成器函数中间可以展厅,可以执行一会歇一会
生成器函数有一个特点,需要加个* 生成器有若干个阶段,如何划分这些阶段呢 yield 定义通过迭代器协议从生成器函数返回的值。如果省略,则返回undefined。
function *go(params) { console.log(1); // 此处的b是提供外界输入进来 // 这一行实现输入和输出,本次的输出放在yield后面,下次的输入放在yield前面 let b = yield 'a'; console.log(2); let c = yield b; console.log(3); return c; } // 生成器函数和普通函数的函数不一样,调用它的话,函数并不会立刻执行 // 它会返回生成器的迭代器,迭代器是一个对象,每调用一次next就可以返回一个值对象 let it = go();; let r1 = it.next(); // 第一次调用next返回一个对象,此对象有两个属性,一个value就是yield后面那个值,一个是done表示是否迭代完成 console.log(r1); //{value:'a',done:false}; // next 第一次执行不需要传参,传参是没有意义的 let r2 = it.next('B值');//传给了b console.log(r2); // {value:'a',done:false}; let r3 = it.next();// {value:undefined,done:true}; console.log(r3); 复制代码
6.5 Promise来处理
Promise 对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。
6.5.1 例子
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(1000) }, 1000); }); p1.then(res=>{ console.log(res) }) 复制代码
6.5.2 promise.all
会接收到promise数组,如果promise全部完成了这个,promise才会成功,如果有一个失败,整体就失败了
同时异步请求多个数据的时候,会用all
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(1000) }, 1000); }); let p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(1000) }, 1000); }); Promise.all([p1,p2]).then(res=>{ console.log('===================================='); console.log(res); console.log('===================================='); },err=>{ console.log('===================================='); console.log(err); console.log('===================================='); }) 复制代码
原理
function gen(times,cb) { let result = [],count=0 return function(i,data){ result[i] = data; if (++count == times) { cb(result); } } } Promise.alls = function (promises) { return new Promise(function (resovle, reject) { let result = [] let count =0 let done = gen(promises.length,resovle) // function done(i,data){ // result[i] = data; // if (++count== promises.length) { // resovle(result) // } // } for (let i = 0; i < promises.length; i++) { // promises[i].then(done.bind(null,i)); promises[i].then(function (data) { done(i,data) }, reject); } }) } Promise.alls([p1,p2]).then(res=>{ console.log('===================================='); console.log(res); console.log('===================================='); },error=>{ console.log('===================================='); console.log(error); console.log('===================================='); }) 复制代码
6.5.3 promise.race
会接收到一个promise数组,只要一个成功,则就成功了,只有一个失败就是失败了
当你有三个接口都不稳定,可你可以同时请求三个接口,谁先回来用谁的
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(1000) }, 1000); }); let p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(1000) }, 1000); }); Promise.race([p1,p2]).then(res=>{ console.log('===================================='); console.log(res); console.log('===================================='); }) 复制代码
原理
Promise.race = function (promises) { return new Promise(function (resovle, reject) { for (let i = 0; i < promises.length; i++) { promises[i].then(resovle, reject); } }) } 复制代码
6.6 Co
co是一个为Node.js和浏览器打造的基于生成器的流程控制工具,借助于Promise,你可以使用更加优雅的方式编写非阻塞代码。
let fs = require('fs'); function readFile(filename) { return new Promise(function (resolve, reject) { fs.readFile(filename, function (err, data) { if (err) reject(err); else resolve(data); }) }) } function *read() { let template = yield readFile('./template.txt'); let data = yield readFile('./data.txt'); return template + '+' + data; } co(read).then(function (data) { console.log(data); }, function (err) { console.log(err); }); 复制代码
function co(gen) { let it = gen(); return new Promise(function (resolve, reject) { !function next(lastVal) { let {value, done} = it.next(lastVal); if (done) { resolve(value); } else { value.then(next, reason => reject(reason)); } }(); }); } 复制代码
6.7 Async/ await
使用 async
关键字,你可以轻松地达成之前使用生成器和co函数所做到的工作 但是其实是它,只要generator+promise语法
Async的优点
- 内置执行器
- 更好的语义
- 更广的适用性
async function timeout() { return 'hello world' } timeout().then(res=>{ console.log(res); }) console.log('虽然在后面,但是我先执行'); 复制代码
async 返回是一个 promise
举一个例子
function loading(num) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(2 * num) }, 2000); } ) } // 这是一个函数构造器 复制代码
这时候我想传入三个值30、50、40 通过计算后求和。 延迟的时间模拟网络请求时间
async function testResult() { let one = await loading(30); let two = await loading(50); let three = await loading(40); console.log(first + second + third); } testResult(); 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 改进异步封装:处理带返回值的异步调用
- 17.12 异步处理
- JavaScript异步处理的那些事儿
- 在 WorkManager 中处理异步任务
- SpringBoot 教程之处理异步请求
- 基于RabbitMQ实现异步消息通知处理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。