-
什么是回调地狱(函数作为参数层层嵌套)
-
什么是回调函数(一个函数作为参数需要依赖另一个函数执行调用)
-
如何解决回调地狱
-
保持你的代码简短(给函数取有意义的名字,见名知意,而非匿名函数,写成一大坨)
-
模块化(函数封装,打包,每个功能独立,可以单独的定义一个js文件Vue,react中通过import导入就是一种体现)
-
处理每一个错误
-
创建模块时的一些经验法则
-
承诺/生成器/ES6等
-
-
Promises:编写异步代码的一种方式,它仍然以自顶向下的方式执行,并且由于鼓励使用try / catch样式错误处理而处理更多类型的错误
-
Generators:生成器让你“暂停”单个函数,而不会暂停整个程序的状态,但代码要稍微复杂一些,以使代码看起来像自上而下地执行
-
Async functions:异步函数是一个建议的ES7功能,它将以更高级别的语法进一步包装生成器和继承
demo
var sayhello = function(callback){ setTimeout(function(){ console.log("hello"); return callback(null); },1000); } sayhello(function(err){ console.log("xiaomi"); }); console.log("mobile phone"); //mobile phone //hello //Xiaomi
我现在利用上面的code 依次打印出xiaomi apple huawei 写出来就是这样的:
var sayhello = function(name, callback){ setTimeout(function(){ console.log("hello"); console.log(name); return callback(null); },1000); } sayhello("xiaomi", function(err){ sayhello("apple", function(err){ sayhello("huawei", function(err){ console.log("end"); }); }); }); console.log("mobile phone");
回调层层嵌套会带来的问题。看起来太蠢了。
解决回调嵌套问题(ES6 promise)
var sayhello = function(name){ return new Promise(function(resolve, reject){ setTimeout(function(){ console.log("hello"); console.log(name); resolve(); },1000); }); } sayhello("xiaomi").then(function(){ console.log('frist'); }).then(function(){ return sayhello("huawei"); console.log('second'); }).then(function(){ console.log('second'); }).then(function(){ return sayhello("apple"); }).then(function(){ console.log('end'); }).catch(function(err){ console.log(err); }) console.log("mobile phone");
ES6 co/yield方案
yield: Generator 函数是协程在 ES6 的实现,而yield是 Generator关键字, 异步操作需要暂停的地方,都用yield语句注明。
co: co 模块是著名程序员 TJ Holowaychuk 于 2013 年 6 月发布的一个小工具,用于 Generator 函数的自动执行。
关于 co/yield详细原理可参考:
http://es6.ruanyifeng.com/#docs/generator-async
什么是Generator 函数?
Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即主动交出执行权,暂停执行)。
整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用yield语句注明。Generator 函数的执行方法如下。
function* gen() { yield console.log("test 1"); yield console.log("test 2"); yield console.log("test 3"); return y; } var g = gen(); g.next()//“test 1” g.next()//“test 2”
Generator 函数不同于普通函数的另一个地方,即执行它不会返回结果,返回的是指针对象。调用指针g的next方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的yield语句,打印”test 1”并暂停,再次调用next会打印”test 2”暂停住,但不会打印”test 3”
Generator 函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因
co模块
Generator 就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权(即调用next)。
两种方法可以做到这一点。
(1)回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
(2)Promise 对象。将异步操作包装成 Promise 对象,用then方法交回执行权,因为resolve后会执行then方法。
co 模块其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个模块。使用 co 的前提条件是,Generator 函数的yield命令后面,只能是 Thunk 函数或 Promise 对象。如果数组或对象的成员,全部都是 Promise 对象,也可以使用 co。co本身就是Promise 对象。
实质co流程可以理解为:
co 检测 传入函数 是否为Generator 。(是,则可以在yield处暂停执行)
co得到Generator函数返回指针,并使用next方法开始执行 Generator 。
第一个异步函数(Promise对象)有了结果,调用了resolve,co使用Promise 对象then的方法接管执行权接着调用next 执行Generator剩余部分,循环调用。
使用
Generator 函数前面要加星号(*)
根据语法规范,yield* 的作用是代理 yield 表达式,将需要函数本身产生(yield)的值委托出去。yield* 后面跟一个生成器函数、或其他可迭代的对象(如一个数组、字符串、arguments对象)。yield* 表达式的返回值,就是其后面可迭代对象迭代完毕时的返回值。
var sayhello = function(name, ms){ return new Promise(function(resolve, reject){ setTimeout(function(){ console.log("hello"); console.log(name); // if (error) return reject(error); resolve("helloworld"); },ms); }); } var gen = function *(){ yield sayhello("xiaomi", 2000); console.log('frist'); yield sayhello("huawei", 1000); console.log('second'); yield sayhello("apple", 500); console.log('end'); } console.log("mobile phone"); co(gen()); console.log("mobile end");
ES7 async/await 方案
async/await是es7的新标准,并且在node7.0中已经得到支持。
它就是 Generator 函数的语法糖,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。可以理解官方对co和Generator 封装方案。
async函数定义如下
async function fn(){ await sayhello(); return 0; }
关键字:async
async关键字用于修饰function,async函数的特征在于调用return返回的并不是一个普通的值,而是一个Promise对象,如果正常return了,则返回Promise.resolve(返回值),如果throw一个异常了,则返回Promise.reject(异常)。
关键字:await
await关键字只能在async函数中才能使用,也就是说你不能在任意地方使用await。await关键字后跟一个promise对象(你想要执行的异步操作),函数执行到await后会退出该函数,直到事件轮询检查到Promise有了状态resolve或reject 才重新执行这个函数后面的内容。
它与直接使用Generator,具有以下优点:
内置执行器,所以不再需要co模块了
async函数的await命令后面,可以是 Promise对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)
async函数的返回值是 Promise 对象
var sayhello = function(name, ms){ return new Promise(function(resolve, reject){ setTimeout(function(){ console.log("hello"); console.log(name); if(name === "huawei") return reject(name); else return resolve("helloworld"); }, ms); }); } async function test() { try { console.log('frist'); await sayhello("xiaomi", 2000); console.log('second'); await sayhello("huawei", 1000); console.log('end'); await sayhello("apple", 500); } catch (err) { console.log('err:'+err); } }; test();
对错误的处理
await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到,而后续的await函数就不会继续执行了。我们应该在将await语句放在try catch代码块中,如果有多个await命令,可以统一放在try…catch结构中,我们通过这种方式对发生的异步错误进行处理。