JS三座大山:原型原型链、做用域闭包、同步异步。
以前有写过本身对闭包的理解,今天来总结一下JS中的异步。ajax
思考(案例来自stackoverflow):编程
function foo(){ var result; $ajax({ url:'...', success:function(response){ result=response; //return response;//tried this one as well } }); return result; } var result=foo();
初学异步的时候,这里是很容易错的地方,你想要获取从服务器端返回的数据,结果却一直undefined。
分析:
JavaScript是单线程语言,可是js中有不少任务耗时比较长,好比ajax请求,若是都按照顺序进行,每每会出现浏览器无响应的状况,因此就须要异步的形式。JS中全部的任务能够分为两种:同步任务和异步任务。json
同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;segmentfault
异步任务:不进入主线程,而进入任务队列中的任务,只有任务队列通知主线程,某个异步任务能够执行了,这个任务才会进入主线程执行。数组
事件循环(Event Loop):只有执行栈中的全部同步任务都执行完毕,系统才会读取任务队列,看看里面的异步任务哪些能够执行,而后那些对应的异步任务,结束等待状态,进入执行栈,开始执行。promise
异步的解决方案:浏览器
下面咱们尝试将上面代码改正一下,几种方法以下:
1.callback服务器
function foo(callback){//定义函数的时候将另外一个函数(回调函数)做为参数传入定义的函数中。 $ajax({ //... success:callback//异步操做执行完毕后,再执行该回调函数,确保回调在异步操做以后执行。 }); } function myCallback(result){ //... } foo(myCallback);
回调函数自己是咱们约定俗成的一种叫法,咱们定义它,可是并不会本身去执行它,它最终被其余人执行了。闭包
优势:比较容易理解;
缺点:1.高耦合,维护困难,回调地狱;2.每一个任务只能指定一个回调函数;3.若是几个异步操做之间并无顺序之分,一样也要等待上一个操做执行结束再进行下一个操做。下图回调地狱(图片来自于新浪微博(@ruanyf)):异步
2.Promise
function ajax(url){ return new Promise(function(resolve,reject){ var xhr=new XMLHttpRequest(); xhr.onload=function(){ resolve(this.responseText); }; xhr.onerror=reject; xhr.open('GET',url); xhr.send(); }); } ajax('/echo/json') .then(function(result){...}) .then(function(){...}) .catch(function(){...});
ES6给咱们提供了一个原生的构造函数Promise,Promise表明了一个异步操做,能够将异步对象和回调函数脱离开来,经过.then方法在这个异步操做上绑定回调函数,Promise可让咱们经过链式调用的方法去解决回调嵌套的问题,并且因为promise.all这样的方法存在,可让同时执行多个操做变得简单。
promise对象存在三种状态:
1)Fulfilled:成功状态
2)Rejected:失败状态
3)Pending:既不是成功也不是失败状态,能够理解为进行中状态
promise对象的两个重要方法:resolve/reject
1)resolve方法可使Promise对象的状态改变为成功,同时传递一个参数用于后续成功后的操做。
2)reject方法能够将Promise对象的状态改变为失败,同时将错误信息传递到后续错误处理的操做。
.then可使用链式调用,缘由在于:每一次执行该方法时总会返回一个Promise对象。
另外,在then的函数当中的返回值,能够做为后续操做的参数(例如:.then(return a).then(console.log(a+b)))
那么问题来了,若是上面代码异步操做抛出错误,会怎么样?会调用catch方法指定的回调函数,处理这个错误,并且then方法指定的回调函数,若是运行中抛出错误,也会被catch捕获。Promise对象的错误具备“冒泡”性质,会一直向后传递,直到被捕获为止,也就是说,错误老是会被下一个catch语句捕获。
理解Promise用法的关键点:
1.then方法是Promise实例的方法,即Promise.prototype上的,它的做用是为Promise实例添加状态改变时的回调函数,这个方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
2.链式中的第二个then开始,它们的resolve中的参数,是前一个then中resolve的return语句的返回值。
3.关于执行顺序:Promise在实例化的时候就会执行,也就是若是Promise的实例化语句中函数console.log输出语句,它会比then中的先执行。Promise.all中传入的Promise对象的数组(假设为p一、p2),即便p2的运行速度比p1快,Promise.all方法仍然会按照数组中的顺序将结果返回。
理解了上面这些方便写原生的Promise,利用观察者模式。后面补充。
Promise的缺点:
1.当处于未完成状态时,没法肯定目前处于哪一阶段。
2.若是不设置回调函数,Promise内部的错误不会反映到外部。
3.没法取消Promise,一旦新建它就会当即执行,没法中途取消。
3.async/await:
不少人说async/await是异步编程的终极解决方案、
JavaScript 的 async/await 实现,离不开 Promise。
var superagent=require('superagent') function delay(){ return new Promise(function(resolve,reject){ setTimeout({ resolve(42); },3000); }) } async function getAllBooks(){ var bookIDs=await superagent.get('/user/books'); await delay(1000); return await superagent.get('/books/ids='JSON.stringify(bookIDs)); } getAllBooks() .then(function(){});
上面的 delay() 没有申明为 async。实际上,delay() 自己就是返回的 Promise 对象,加不加 async 结果都同样。
只要在函数名以前加上async关键字,就代表这个函数内部有异步操做。这个异步操做返回一个Promise对象,前面用await关键字注明。函数执行的时候,一旦遇到await,就会先执行await后面的表达式中的内容(异步),再也不执行函数体后面的语句。等到异步操做执行完毕后,再自动返回到函数体内,继续执行函数体后面的语句。
下面这段来自:https://segmentfault.com/a/11...
async:定义异步函数
1)自动把函数转换为Promise
2)当调用异步函数时,函数返回值会被resolve处理
3)异步函数内部可使用await
await:暂停异步函数的执行
1)当使用在Promise前面时,await等待Promise完成,并返回Promise的结果
2)await只能和Promise一块儿使用,不能和callback一块儿使用
3)await只能用在async函数中
async/await并不会取代promise,由于async/await底层依然使用promise。
async function getABC(){ let A = await getValueA(); // getValueA 花费 2 秒 let B = await getValueB(); // getValueA 花费 4 秒 let C = await getValueC(); // getValueA 花费 3 秒 return A*B*C }
每次遇到 await 关键字时,Promise 都会停下在,一直到运行结束,因此总共花费是 2+4+3 = 9 秒。await 把异步变成了同步。
async function getABC() { // Promise.all() 容许同时执行全部的异步函数 let results = await Promise.all([ getValueA, getValueB, getValueC ]); return results.reduce((total,value) => total * value); }
函数总耗时为 4 秒(getValueB 的耗时)。
Async 的价值在于用写同步的方式写异步,1避免了阻塞,2必免写回调
async/await详细了解,推荐:https://segmentfault.com/a/11...