-
什么是回调地狱(回调金字塔)?
由于javascript是单线程执行的,所以在javascript中会经常使用异步回调函数,异步回调函数只有在请求完成之后才会执行,多层嵌套的回调函数不仅影响性能,也会降低代码阅读率和增加代码维护成本。例如以下代码:
var sayhello = function(name, callback){ setTimeout(function(){ console.log(name); return callback(null); },1000); } console.log("回调函数开始"); sayhello("回调函数1", function(err){ sayhello("回调函数2", function(err){ sayhello("回调函数3", function(err){ console.log("回调结束"); }); }); });
执行结果如下:
回调函数开始 回调函数1 回调函数2 回调函数3 回调结束
以上代码中三层嵌套的回调函数,代码看起来非常冗余,随着嵌套层数的增加,代码冗余度也会越来越大,性能和可维护性也会降低,所以不建议通过“回调地狱”的形式实现上述功能。
-
怎么解决回调地狱?
解决回调地狱的常见方法有以下4种:
-
发布者订阅模式
发布订阅模式是利用一个消息中心,发布者发布一个消息给消息中心,订阅者从消息中心订阅该消息,类似于 vue 的父子组件之间的传值。
发布订阅模式demo如下:
//订阅done事件 $('#app').on('done',function(data){ console.log(data) }) //发布事件 $('#app').trigger('done,'haha')
-
Promise
Promise 实际就是一个对象, 从它可以获得异步操作的消息,Promise 对象有三种状态,pending(进行中)、fulfilled(已成功)和rejected(已失败)。Promise 的状态一旦改变之后,就不会在发生任何变化,通过链式调用的方式调用回调函数。
Promise封装异步请求demo如下:
export default function getMethods (url){ return new Promise(function(resolve, reject){ axios.get(url).then(res => { resolve(res) }).catch(err =>{ reject(err) }) }) } getMethods('/api/xxx').then(res => { console.log(res) },err => { console.log(err) })
-
Generator(ES6)
Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,使用该对象的 next() 方法,可以遍历 Generator 函数内部的每一个状态,直到 return 语句。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式, yield是暂停执行的标记。
next() 方法遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
Generator 的 demo:
function *generatorDemo() { yield 'hello'; yield 1+4; return 'ok'; } var demo = generatorDemo() demo.next() // { value: 'hello', done: false } demo.next() // { value: 5, done: false } demo.next() // { value: 'ok', done: ture } demo.next() // { value: undefined, done: ture }
-
async await(ES7)
async函数返回的是一个 Promise 对象,可以使用 then 方法添加回调函数,async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
await命令后面返回的是 Promise 对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中。
async await 内部代码从右向左,先执行await等待的结果(await 右侧),发现有await关键字,则让出线程,阻塞代码。await要等待的结果,如果不是 promise , await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完,再回到async内部,把这个非promise的东西,作为 await表达式的结果。
如果它等到的是一个 promise 对象,await 也会暂停async后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。
async 的 demo:
async function demo(){ try{ await new Promise(function(resolve, reject) { // do something ... }); }catch (err) { console.log(err); } } demo().then(data =>{ console.log(data) })
-
-
异步编程测试题目:
async function async1() { console.log( 'async1 start') await async2() console.log( 'async1 end' ) } async function async2() { console.log( 'async2') } console.log( 'script start') setTimeout( function () { console.log( 'setTimeout' ) }, 0) async1(); new Promise( function( resolve ) { console.log( 'promise1' ) resolve(); }).then( function() { console.log( 'promise2') }) console.log( 'script end')
在浏览器中的输出结果如下:
script start async1 start async2 promise1 script end async1 end promise2 setTimeout