多回调问题
前端编程时,大多通过接口交换数据,接口调用都是异步的,处理数据都是在回调函数里.
假如需要为一个用户建立档案,需要准备以下数据,然后调用建档接口
name // 用户名字 使用接口 get_name(userid)
files // 用户附件 使用接口 get_atta(userid,name,location)
create_record(userid,name,location) // 调用建档接口
create_files(recordId,userid,files) // 上传用户文件
要解决这个问题,可以先调用name接口,在其回调里调用addr接口,然后在addr的回调里调用create_record.....
这难以想象,代码会写得很长,回调嵌套多层.虽然能解决问题,但是,程序代码顺序执行的思维,很难理清这种问题,如果回调再多一些,代码就难以维护.一看就头晕.
也许同步是入门异步是进阶,前者看,后者思
同步方法
依然使用程序顺序执行的思路,将代码顺序写出,(假如接口是同步得到结果的)
function createRecord(userid){
var name = get_name(userid); var files = get_atta(userid,name,location); var recordId = create_record(userid,name,location); create_files(recordId,userid,files);
}
异步方法时,不会得到正确结果,但如果设法让上述代码顺序执行,执行完第1个方法再执行第2个...,也就是同步的思路.那么就能得到正确结果.
顺序等待
等待前面的方法回调后,再执行后面的方法.
let user = { userid: 1 } // 保存参数 let isOk = false;// 全部成功时,设为true
// function createRecord() {
// 获取名字 if (!user.hasOwnProperty('name')) {
// 异步方法 参数1:数据 2:成功时执行 3:失败时执行 get_name(user, (res) => {
user.name = res.Name;
console.log('获取名字成功');
// 成功得到结果后,递归调用.由于if条件,递归时不会重复执行 createRecord(); }, (err) => { // 不成功时,方法结束 });
// 保证异步成功返回时,才执行后续方法 return; }
// 获取文件数据 if (!user.hasOwnProperty('files')) { get_atta(user, (res) => { user.files = res.Attas;
console.log('获取文件成功') createRecord(); }, (err) => { // }); return; } // 建档接口,返回ID if (!user.hasOwnProperty('recordId')) { .....return; } // 调用文档接口,有文件时才上传 if (user.files.length > 0) { create_files(user, (res) => {
// 最后一个接口,当成功返回时,表示全部请求成功了
console.log('上传文件成功') isOk = true; }, (err) => { // }); } else { isOk = true; } }
每次递归时,只会执行其中一个IF块,IF块就是接口调用,当成功返回时,刷新IF条件,继续递归,失败时不递归.
如此,模拟了"顺序执行".在思路上比较清晰,和同步方法顺序执行的思路一至.
此法缺点多,首先此法是同步接口方法的在形式上的生搬硬套.将所有请求接口的方法按顺序写在一个方法内,使用IF条件判断是否执行,使用递归反复执行.
IF块代码较长时,较多时,整个方法就很长,难以维护.方法作用旨在"控制每个接口方法的顺序执行",只是个代码的容器.
条件逻辑复杂,如果判定条件写错了,或者忘了刷新判定条件,很容易造成无限递归,程序崩溃.
顺序调用
将IF块写成独立方法,在其中调用下一个步骤的方法,直到调用最后一个方法.
let user = { userid: 1 } // 保存参数 let isOk = false;// 全部成功时,设为true // 获取名字 function getName(user) { get_name(user, (res) => { user.name = res.Name; // 成功后,调用获取文档方法 getAtta(user); }); } // 获取文档 function getAtta(user) { get_atta(user, (res) => { user.files = res.Attas; // 成功后,调用建档方法 createRecord(user); }) } // 建立档案 function createRecord(user) { create_record(user, (res) => { user.recordId = res.RecordId; // 成功后,调用上传文件方法 createFiles(user); }) } // 上传文档方法 function createFiles(user) { if (user.files.length == 0) { isOk = true; return; } create_files(user, (res) => { isOk = true; }) } // 调用 getName(user);
这个办法与递归法相比,不将所有方法写到一个方法内,反而拆分为独立方法,每个方法再调用下个方法.最后完成所有请求.
没有条件语句逻辑更清晰明了,各接口方法完成各自取数据任务,有一个起始方法,和最后一个结束方法,链环式的.按顺序一个接一个调用的.如果其中一个出错,那么终止.
各方法独立,依然按照递归法的顺序调用后续方法,相比所有方法写在一起的递归法,只是在形式上分开了,也许好维护些,但缺少"封装"性.
容器方法
结合前两种办法的特点,造一个类管理方法的执行,返回值以及出错信息.
function moreAjax() { let self = this; // 全部成功时为true self.AllOk = false; // 方法容器 let ajaxList = []; // 每个方法成功时返回值容器 self.ResList = []; // 每个方法错误时返回值容器 self.ErrList = []; // 方法执行序列 let index = 0; // 添加方法 self.add = function (method) { ajaxList.push(method); } // 开按执行 按add时顺序执行 self.start = function () { if (index == ajaxList.length) { // 全部成功标识 self.AllOk = true; return; } // ajaxList[index]( (res) => { console.log('(success)执行序号' + index); // 保存回调结果 self.ResList.push(res); // 递归调用,执行下个方法 index = index + 1; self.start(); }, (err) => { console.log('(error)执行序号' + index); // 保存出错结果 self.ErrList.push(err); }); } } // 调用 let test = new moreAjax(); // 添加方法 test.add((success, error) =>{ get_name( (res)=>{success(res)}, (err)=>{success(err)} ) }) // 再添加方法 test.add((success, error) =>{ get_location( (res)=>{success(res)}, (err)=>{success(err)} ) }) // 添加最后一个方法 test.add((success, error) =>{ success("lastMethod"); if (send.AllOk) { console.log(send.ResList); } else { console.log(send.ErrList); } }) // 启动 test.start();
容器法是前两种办法的优化,兼顾"封装"性和灵活性.
add方法添加接口方法,方法要求前两个参数第1个用于成功时执行,第2个用于失败时执行. 每调用一次add,则向ajaxList加入一个方法.
start方法执行后,方法会按add的顺序开始执行,每个方法执行完成时,若成功会记录返回值,若失败记录失败信息.通过ResList和ErrList数组属性,访问这些值.
如果每个方法都正确返回,那么会执行到ajaxList里的最后一个方法,如果其中一个失败,则会停止.
每个方法成功时,ResList增加一个结果.第一个方法成功时,结果值为 ResList[0] 之后的方法通过此属性可访问之前方法返回的结果.