JavaScript的执行机制在上篇文章中进行了深刻的探讨,那么既然是一门单线程语言,如何进行良好体验的异步编程呢
当程序跑起来时,通常状况下,应用程序(application program)会时常经过API调用库里所预先备好的函数。可是有些库函数(library function)却要求应用先传给它一个函数,好在合适的时候调用,以完成目标任务。这个被传入的、后又被调用的函数就称为回调函数(callback function)。node
"调用"在发出以后,这个调用就直接返回了,因此没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会马上获得结果。而是在"调用"发出后,"被调用者"经过状态、通知来通知调用者,或经过回调函数处理这个调用。异步调用发出后,不影响后面代码的执行。
简单说就是一个任务分红两段,先执行第一段,而后转而执行其余任务,等作好了准备,再回过头执行第二段。
在异步执行的模式下,每个异步的任务都有其本身一个或着多个回调函数,这样当前在执行的异步任务执行完以后,不会立刻执行事件队列中的下一项任务,而是执行它的回调函数,而下一项任务也不会等当前这个回调函数执行完,由于它也不能肯定当前的回调合适执行完毕,只要引它被触发就会执行,es6
异步最先的解决方案是回调函数,如事件的回调,setInterval/setTimeout中的回调。可是回调函数有一个很常见的问题,就是回调地狱的问题
下面这几种都属于回调ajax
异步回调嵌套会致使代码难以维护,而且不方便统一处理错误,不能 try catch会陷入回调地狱express
fs.readFile(A, 'utf-8', function(err, data) { fs.readFile(B, 'utf-8', function(err, data) { fs.readFile(C, 'utf-8', function(err, data) { fs.readFile(D, 'utf-8', function(err, data) { //.... }); }); }); }); ajax(url, () => { // 处理逻辑 ajax(url1, () => { // 处理逻辑 ajax(url2, () => { // 处理逻辑 }) }) })
Promise 必定程度上解决了回调地狱的问题,Promise 最先由社区提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。编程
const promise = new Promise((resolve, reject) => { // 异步处理 // 处理结束后、调用resolve 或 reject });
// onFulfilled 参数是用来接收promise成功的值, // onRejected 参数是用来接收promise失败的缘由 //两个回调返回的都是promise,这样就能够链式调用 promise.then(onFulfilled, onRejected);
const promise = new Promise((resolve, reject) => { resolve('fulfilled'); // 状态由 pending => fulfilled }); promise.then(result => { // onFulfilled console.log(result); // 'fulfilled' }, reason => { // onRejected 不会被调用 })
Promise对象的then方法返回一个新的Promise对象,所以能够经过链式调用then方法。then方法接收两个函数做为参数,第一个参数是Promise执行成功时的回调,第二个参数是Promise执行失败时的回调。两个函数只会有一个被调用,函数的返回值将被用做建立then返回的Promise对象。这两个参数的返回值能够是如下三种状况中的一种:promise
//对应上面第一个node读取文件的例子 function read(url) { return new Promise((resolve, reject) => { fs.readFile(url, 'utf8', (err, data) => { if(err) reject(err); resolve(data); }); }); } read(A).then(data => { return read(B); }).then(data => { return read(C); }).then(data => { return read(D); }).catch(reason => { console.log(reason); });
//对应第二个ajax请求例子 ajax(url) .then(res => { console.log(res) return ajax(url1) }).then(res => { console.log(res) return ajax(url2) }).then(res => console.log(res))
能够看到,Promise在必定程度上其实改善了回调函数的书写方式,最明显的一点就是去除了横向扩展,不管有再多的业务依赖,经过多个then(...)来获取数据,让代码只在纵向进行扩展;另一点就是逻辑性更明显了,将异步业务提取成单个函数,整个流程能够看到是一步步向下执行的,依赖层级也很清晰,最后须要的数据是在整个代码的最后一步得到。
因此,Promise在必定程度上解决了回调函数的书写结构问题,但回调函数依然在主流程上存在,只不过都放到了then(...)里面,和咱们大脑顺序线性的思惟逻辑仍是有出入的。浏览器
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数彻底不一样
Generator 函数有多种理解角度。语法上,首先能够把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,仍是一个遍历器对象生成函数。返回的遍历器对象,能够依次遍历 Generator 函数内部的每个状态。形式上,Generator 函数是一个普通函数,可是有两个特征。app
Generator 函数的调用方法与普通函数同样,也是在函数名后面加上一对圆括号。不一样的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。
下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法能够恢复执行。异步
function* foo () { var index = 0; while (index < 2) { yield index++; //暂停函数执行,并执行yield后的操做 } } var bar = foo(); // 返回的实际上是一个迭代器 console.log(bar.next()); // { value: 0, done: false } console.log(bar.next()); // { value: 1, done: false } console.log(bar.next()); // { value: undefined, done: true }
能够看到上个例子当中咱们须要一步一步去调用next这样也会很麻烦,这时咱们能够引入co来帮咱们控制
Co是一个为Node.js和浏览器打造的基于生成器的流程控制工具,借助于Promise,你可使用更加优雅的方式编写非阻塞代码。
Co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,能够跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操做)。
说白了就是帮你自动执行你的Generator不用手动调用nextasync
咱们能够经过 Generator 函数解决回调地狱的问题,能够把以前的回调地狱例子改写为以下代码:
const co = require('co'); co( function* read() { yield readFile(A, 'utf-8'); yield readFile(B, 'utf-8'); yield readFile(C, 'utf-8'); //.... } ).then(data => { //code }).catch(err => { //code });
function *fetch() { yield ajax(url, () => {}) yield ajax(url1, () => {}) yield ajax(url2, () => {}) } let it = fetch() let result1 = it.next() let result2 = it.next() let result3 = it.next()
async 函数是Generator 函数的语法糖,是对Generator作了进一步的封装。
async function async1() { return "1" } console.log(async1()) // -> Promise {<resolved>: "1"}
重点:遇到 await 表达式时,会让 async 函数 暂停执行,等到 await 后面的语句(Promise)状态发生改变(resolved或者rejected)以后,再恢复 async 函数的执行(再以后 await 下面的语句),并返回解析值(Promise的值)
promise就是作这件事的 , 它会自动等到Promise决议之后的返回值,resolve(...)或者reject(...)均可以。
async内部会在promise.then(callback),回调函数里调用 next()... (还有用Thunk的, 也是为了作这个事的);
简单说 , async/awit 就是对上面gennerator自动化流程的封装 , 让每个异步任务都是自动化的执行 , 当第一个异步任务readFile(A)执行完如上一点说明的, async内部本身执行next(),调用第二个任务readFile(B);
这里引入ES6阮一峰老师的例子 const fs = require('fs'); const readFile = function (fileName) { return new Promise(function (resolve, reject) { fs.readFile(fileName, function(error, data) { if (error) return reject(error); resolve(data); }); }); }; async function read() { await readFile(A);//执行到这里中止往下执行,等待readFile内部resolve(data)后,再往下执行 await readFile(B); await readFile(C); //code } //这里可用于捕获错误 read().then((data) => { //code }).catch(err => { //code });