前言javascript
javascript是单线程的一门语言,因此在执行任务的时候,全部任务必须排队,而后一个一个的执行, 在javascript中有分同步代码,和异步代码,顾名思义,同步代码,就是依此执行的代码,异步代码可能不会当即执行,得等到某一特定事件触发时才会执行,javascript有个任务队列,用来存放异步代码,任务队列中的任务又有优先级之分,微任务(microtask)的优先级大于宏任务(macrotask),在javascript中代码的执行顺序为,主线程会先执行完同步代码,并将异步代码放到任务队列中,当同步代码执行完毕,就轮询任务队列,先询问微任务,若是有则执行微任务,若是没有询问宏任务。php
//异步代码
setTimeout(function () { //属于宏任务
console.log('hello world3');
},0);
new Promise(resolve => { //属于微任务
console.log('hello world4'); //Promise 对象会当即执行 因此new Promise里面的相似与同步代码
resolve('hello world5');
}).then(data => {console.log(data)});
//同步代码
function main(){
console.log('hello world');
}
console.log('hello world1');
console.log('hello world2');
main();
复制代码
输出结果为:html
hello world4
hello world1
hello world2
hello world
hello world5
hello world3
复制代码
按照上面所说的顺序,同步代码先执行,那么会先输出hello world4 而后hello world1 ,hello world2,hello world 接下来执行任务队列的异步代码,先轮询微任务是否有要执行的代码,因为Promise对象属于微任务的,故先执行它,输出hello world5 ,而后执行宏任务的代码,及setTimeout的代码,输出hello world3java
本例比较简单,讲述了一下javascript代码的执行流程,但愿对理解异步有帮助,其中涉及的Promise对象会在本文详细介绍。node
本文代码可能比较多,全部涉及的代码均在个人github上python
接下来回归正题,Javascript中异步的5种实现方法,并以ajax等为例子,实现几种异步的编写方式 javascript中的异步实现方式有如下几种git
1.callback (回调函数)es6
回调函数是Javascript异步编程中最多见的,因为JavaScript中的函数是一等公民,能够将其以参数形式传递,故就有了回调函数一说,熟悉nodejs的人知到,里面涉及很是多的回调,这些回调表明着,当某个任务处理完,而后须要作的事,好比像一些动画处理,当动画走完,而后执行回调,或者链接数据库等,举个例子github
function load(url,callback){
//something
setTimeout(callback,3000);//假设某个异步任务处理须要3s 3s后执行回调
}
load('xxx',function() {
//do something
console.log('hello world')
})
复制代码
3s执行回调,回调的内容本身决定
再来看个ajax例子 (代码 )
//ajax_callback.js
function ajax(object, callback) {
function isFunction(func) { // 是否为函数
return typeof func === 'function';
}
function isObject(object) { //是否为对象
return typeof object === 'object';
}
function toQuerystring(data) { //对象转成查询字符串 例如{a:1,b:2} => a=1&b=2 或{a:[1,2],b:3} => a=1&a=2&b=3
if (!isObject(data) || !data) throw new Error('data not object');
var result = '';
for (var key in data) {
if (data.hasOwnProperty(key)) {
if (isObject(data[key]) && !Array.isArray(data[key])) throw new Error('not support error');//除去对象
if (Array.isArray(data[key])) {
data[key].forEach(function (v) {
result += key + '=' + v + '&'
});
} else {
result += key + '=' + data[key] + '&';
}
}
}
return result.substr(0, result.length - 1);//去掉末尾的&
}
var url = object.url || '';
var method = object.method.toUpperCase() || 'GET';
var data = object.data || Object.create(null);
var async = object.async || true;
var dataType = object.dataType || 'json';//相应的数据类型 可选json ,text, xml
var xhr = new XMLHttpRequest();
url = ajax.baseUrl + url;
data = toQuerystring(data);
method === 'GET' && (url += '?' + data) && (data = null); //get 请求 => url 后面加上 ?a=1&b=2这种
try {
xhr.open(method, url, async);
method === 'POST' && (xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'));//post请求须要设置请求头为 application/x-www-form-urlencoded 类型
console.log(data);
xhr.send(data);
xhr.onreadystatechange = function () {//监听事件
if (this.readyState === 4) {
if (this.status === 200)
if (isFunction(callback))
switch (dataType) {
case 'json': {
callback(JSON.parse(this.responseText));//完成时执行传进来的回调
break
}
case 'text': {
callback(this.responseText);
break
}
case 'xml': {
callback(this.responseXML);
break
}
default: {
break;
}
}
}
}
} catch (e) {
console.log(e);
}
}
ajax.get = function (url, data, callback) { //get方法
this({url: url, method: 'GET', data: data}, callback);
};
ajax.post = function (url, data, callback) { //post方法
this({url: url, method: 'POST', data: data}, callback);
};
ajax.baseUrl = '';
复制代码
以上是个完整的ajax实例,当ajax完成执行回调 一下是使用koa实现的一个简易的服务端,模拟处理ajax的响应,以后的例子都会用这个来模拟ajax响应
//koa_test_server.js
const Koa = require('koa');
const Router = require('koa-router');
const bodyparser = require('koa-bodyparser');
const app = new Koa();
const api = new Router();
api.get('/api/test1', async ctx => { //处理get请求
ctx.res.setHeader('Access-Control-Allow-Origin', '*');//容许跨域访问
let querystring = ctx.querystring;
console.log(querystring);
ctx.body = JSON.stringify({
errno: false,
data: 'it is ok',
message: `you send me ${querystring} type is GET`
});
}).post('/api/test2', async ctx => {//处理post请求
ctx.res.setHeader('Access-Control-Allow-Origin', '*');//容许跨域访问
let data = ctx.request.body;
console.log(data);
ctx.body = JSON.stringify({
errno: false,
data: 'it is ok',
message: `you send me ${JSON.stringify(data)} type is POST`
})
});
app.use(bodyparser());
app.use(api.routes()).use(api.allowedMethods());
app.listen(3000, () => {
console.log('listen in port 3000')
});
复制代码
简单使用以下
//test.html
ajax.baseUrl = 'http://localhost:3000';
ajax.get('/api/test1',{name: 'dpf', age: 19},function (data) {
//do something such as render page
console.log(data);
});
ajax.post('/api/test2',{name: 'youname', age: 19}, function (data) {
//do something such as render page
console.log(data);
});
复制代码
结果以下:
回调的好处就是容易编写,缺点就是过多的回调会产生回调地狱,代码横向扩展,代码可读性变差 不过回调还有不少应用,并且回调也是最经常使用的实现Javascript异步的方式。
2.发布订阅模式
发布订阅模式是设计模式的一种,并非javascript特有的内容,因此javascript能够用发布订阅模式来作异步,那么其余语言如C++ java python php 等天然也能。
简单介绍一下发布订阅模式,发布订阅是两个东西,即发布和订阅,想象一下,有家外卖,你能够点外卖,这就是订阅,当你的外卖作好了,就会有人给你打电话叫你去取外卖,这就是发布,简单来讲,发布订阅模式,有一个事件池,用来给你订阅(注册)事件,当你订阅的事件发生时就会通知你,而后你就能够去处理此事件,模型以下
接下来简单实现这个发布订阅模式
//async_Event.js
//单对象写法 Event 就至关于事件中心
const Event = function () { //使用闭包的好处 : 把EventPool私有化,外界没法访问EventPool
const EventPool = new Map();//使用es6 map来存 event,callback 键值对
const isFunction = func => typeof func === 'function';
const on = (event, callback) => { //注册事件
EventPool.get(event) || EventPool.set(event, []);
if (isFunction(callback)) {
EventPool.get(event).push(callback);
}
else {
throw new Error('callback not is function')
}
};
const addEventListenr = (event, callback) => { //on方法别名
on(event, callback)
};
const emit = (event, ...args) => { //触发(发布)事件
//让事件的触发为一个异步的过程,即排在同步代码后执行
//也能够setTimeout(fn,0)
Promise.resolve().then(() => {
let funcs = EventPool.get(event);
if (funcs) {
funcs.forEach(f => f(...args))
} else {
throw new Error(`${event} not register`)
}
})
};
const send = (event, ...args) => {//emit方法别名
emit(event,...args)
};
const removeListener = event => {//删除事件
Promise.resolve(() => {//删除事件也为异步的过程
if(event){
EventPool.delete(event)
}else{
throw new Error(`${event} not register`)
}
})
};
return {
on, emit, addEventListenr, send
}
}();
复制代码
简单使用
Event.on('event', data => {
console.log(data)
});
setTimeout(() => {
Event.emit('event','hello wrold')
},1000);
复制代码
1s后触发事件,输出hello world
使用发布订阅模式,修改以前的ajax例子
......
xhr.onreadystatechange = function () {//监听事件
if (this.readyState === 4) {
if (this.status === 200)
switch (dataType) {
case 'json': {
Event.emit('data '+method,JSON.parse(this.responseText));//触发事件
break
}
case 'text': {
Event.emit('data '+method,this.responseText);
break
}
case 'xml': {
Event.emit('data '+method,this.responseXML);
break
}
default: {
break;
}
}
}
}
......
复制代码
使用以下
//test.html
//注册事件
Event.on('data GET',data => {
//do something such as render page
console.log(data)
});
Event.on('data POST',data => {
//do something such as render page
console.log(data)
});
//使用ajax
ajax.baseUrl = 'http://localhost:3000';
ajax.get('/api/test1',{name: 'dpf', age: 19});
ajax.post('/api/test2',{name: 'youname', age: 19});
复制代码
使用发布订阅模式的好处是事件集中管理,修改方便,缺点就是,代码可读性降低,事件容易冲突。
3.Promise对象
Promise对象是异步编程的一种解决方案,比传统的回调函数和事件更合理更强大。 Promise,简单说就是一个容器,里面保存着某个将来才会结束的事件的结果,相比回调函数,Promise提供统一的API,各类异步操做均可以用一样的方法进行处理。 Promisel对象的两个特色:
##1.对象状态不受外界影响。Promise对象有三种状态:pending(进行中),fulfilled(已成功),rejected(已失败),当异步操做有结果时能够指定pending状态到fulfilled状态或pending状态到rejected状态的转换,状态一旦变为fulfilled,或rejected则这个Promise对象状态不会在改变。
##2.一旦状态改变,就再也不变化,任什么时候候均可以获得这个结果。
基本格式
let promise = new Promise((resolve, reject) => {//Promise对象接受一个函数
try {
setTimeout(() => {//模拟某异步操做 , 若操做成功返回数据
resolve('hello world'); //resolve() 使pending状态变为 fulfilled,须要注意resolve()函数最多只能接收1个参数,若要传多个参数,须要写成数组,或对象,好比resolve([1,2,2,2])或resolve({data,error})
reject(); //状态已变为fulfilled 故下面这个reject()不执行
}, 1000);
}catch (e) {
reject(e) //操做失败 返回Error对象 reject() 使pending状态变为rejected
}
});
promise.then((data) => {
console.log(data) //resolve()函数里面传的值
},(err) => {
console.log(err) //reject()函数里传的值
});
复制代码
1s后输出hello world Promise对象的几个方法
##1. then(fulfilled,rejected)方法: 异步任务完成时执行的方法,其中fulfilled(data)和rejected(err)分别是单参的回调函数,fulfilled对应的是成功时执行的回调,rejected对应的是失败时执行的回调,fulfilled函数的所接参数为resolve()函数传的值,rejected函数的参数则为reject()函数所传的值。
##2. catch(rejected)方法: then(null,rejected)的别名 捕获Promise对象中的错误
##3. Promise.resolve(data):等价于new Promise(resolve => {resolve(data)})
##4.Promise.all([promise1,promise2,...,promisen]): 用于多个Promise对象的执行,执行时间取最慢的那个,例如:
let promise1 = new Promise(resolve => {
setTimeout(() => {
resolve(1);
}, 1000);
});
let promise2 = new Promise(resolve => {
setTimeout(() => {
resolve(2)
}, 2000)
});
let promise3 = new Promise(resolve => {
setTimeout(() => {
resolve(3)
}, 3000)
});
let start = Date.now();
Promise.all([promise1, promise2, promise3]).then(([data1, data2, data3]) => {//使用数组解构得到每一个Promise对象的data
console.log(`datas = ${data1},${data2},${data3} total times = ${Date.now() - start}ms`);
});
复制代码
输出结果为 datas = 1,2,3 total times = 3000ms
##5.Promise.race([promise1,promise2,...,promisen]): 和Promise.all相似,不过它取Promise对象中最快的那个。
##6.Promise.reject(err): 等价于new Promise((resolve,reject) => reject(err))
对有了Promise对象有了基本的理解,而后能够用它来替代回调函数的模式,好比一个图片加载例子
//回调形式
function asyncLoadImage_callback(url,callback) {//异步加载图片
var proxyImage = new Image();//图片代理
proxyImage.src = url;
proxyImage.onload = callback;//加载完时执行回调
}
asyncLoadImage_callback('xxx', function () {
image.src = 'xxx'//让真正的图片对象显示
});
//Promise对象形式
function asyncLoadImage_Promise(url) {
return new Promise((resolve,reject) => {
var proxyImage = new Image();
proxyImage.src = url;
proxyImage.onload = resolve;
proxyImage.onerror = reject;
})
}
asyncLoadImage_Promise('xxx')
.then(() => {
image.src = 'xxx'//让真正的图片对象显示
}).catch(err => console.log(err));
复制代码
使用Promise对象的好处比较明显,除了写起来有一些麻烦而已。
接下来将介绍将回调函数形式与Promise对象形式的相互转换
##1.回调函数形式转换为Promise对象形式
//promisify.js
//callback => Promise
/**
*
* @param fn_callback
* @returns {function(...[*]): Promise<any>}
*/
function promisify(fn_callback) { //接收一个有回调函数的函数,回调函数通常在最后一个参数
if(typeof fn_callback !== 'function') throw new Error('The argument must be of type Function.');
return function (...args) {//返回一个函数
return new Promise((resolve, reject) => {//返回Promise对象
try {
if(args.length > fn_callback.length) reject(new Error('arguments too much.'));
fn_callback.call(this,...args,function (...args) {
args[0] && args[0] instanceof Error && reject(args[0]);//nodejs的回调,第一个参数为err, Error对象
args = args.filter(v => v !== undefined && v !== null);//除去undefined,null参数
resolve(args)
}.bind(this));//保证this仍是原来的this
} catch (e) {
reject(e)
}
})
}
}
复制代码
简单使用
//nodejs的fs.readFile为例
let asyncReadFile = promisify(require('fs').readFile);
asyncReadFile('async.js').then(([data]) => {
console.log(data.toString());
}, err => console.log(err));
//将上面的asyncLoadImage_callback转换为例
let asyncLoadImage = promisify(asyncLoadImage_callback);
asyncLoadImage.then(() => {
image.src = 'xxx'//让真正的图片对象显示
});
复制代码
##2. Promise对象形式转换为回调函数形式
//callbackify.js
//Promise => callback
/**
*
* @param fn_promise
* @returns {Function}
*/
function callbackify(fn_promise) {
if(typeof fn_promise !== 'function') throw new Error('The argument must be of type Function.');
return function (...args) {
let callback = args.pop();//返回一个函数 最后一个参数是回调
if(typeof callback !== 'function') throw new Error('The last argument must be of type Function.');
if(fn_promise() instanceof Promise){
fn_promise(args).then(data => {
callback(null,data)//回调执行
}).catch(err => {
callback(err,null)//回调执行
})
}else{
throw new Error('function must be return a Promise object');
}
}
}
复制代码
简单使用
let func = callbackify(timer => new Promise((resolve, reject) => {
setTimeout(() => {resolve('hello world')},timer);
}));
func(1000,function (err,data) {
console.log(data)//1s后打印hello world
});
复制代码
接下来对以前的ajax例子进行改写,将回调形式变为Promise形式,能够直接改写,或使用promisify函数
##第一种方式
//ajax_promise.js
function ajax(object) {
return new Promise(function (resolve,reject) {
....
try {
....
xhr.onreadystatechange = function () {//监听事件
if (this.readyState === 4) {
if (this.status === 200) {
switch (dataType) {
case 'json': {
resolve(JSON.parse(this.responseText));
break
}
case 'text': {
resolve(this.responseText);
break
}
case 'xml': {
resolve(this.responseXML);
break
}
default: {
break;
}
}
}else{
reject(new Error('error'))
}
}
}
} catch (e) {
reject(e)
}
});
}
ajax.get = function (url, data) { //get方法
return this({url: url, method: 'GET', data: data});
};
ajax.post = function (url, data) { //post方法
return this({url: url, method: 'POST', data: data});
};
ajax.baseUrl = '';
复制代码
简单使用
//test.html
ajax.baseUrl = 'http://localhost:3000';
ajax.get('/api/test1',{name: 'dpf', age: 19}).then(data => {
console.log(data)
});
ajax.post('/api/test2',{name: 'youname', age: 19}).then(data => {
console.log(data)
});
复制代码
##第二种方式
//test.html
ajax = promisify(ajax);
ajax.baseUrl = 'http://localhost:3000';
ajax.get = (url,data) => ajax({url: url, method: 'GET', data: data});
ajax.post = (url,data) => ajax({url: url, method: 'POST', data: data});
ajax.get('/api/test1', {name: 'dpf', age: 19}).then(([data]) => {
console.log(data)
});
ajax.post('/api/test2', {name: 'youname', age: 19}).then(([data]) => {
console.log(data)
});
复制代码
Promise对象目前是比较流行的异步解决方案,相比回调函数而言,代码再也不横向扩展,并且没有回调地狱这一说,好处仍是挺多的,不过也有不足,就是写起来费劲(相比回调而言),不过Promise对象仍然是javascript的一个重要的知识点,但愿经过刚刚的讲解,读者能对Promise对象有个基本的认识。
4.Generator(生成器)函数 Generator函数是ES6提供的一种异步编程解决方案,其行为相似于状态机。 一个简单的例子
function *gen(){//声明一个生成器
let t1 = yield "hello"; //yield 表示 产出的意思 用yield来生成东西
console.log(t1);
let t2 = yield "world";
console.log(t2);
}
let g = gen();
/*next()返回一个{value,done}对象,value为yield表达式后面的值,done取值为true/false,表示是否 *生成结束*/
let x = g.next();//{value:"hello",done:false} 启动生成器
/**
* 经过给next()函数里传值 这里的值会传递到第一个yield表达式里 即至关于gen函数里 let t1 = "aaaa" */
let y = g.next("aaaa");//{value:"world",done:false}
g.next("bbbb");//{value:undefined,done:true}
console.log(x.value,y.value);
复制代码
输出
aaaa
bbbb
hello world
复制代码
上面的例子中,若是把gen函数当成一个状态机,则经过调用next()方法来跳到下一个状态,即下一个yield表达式,给next()函数传值来把值传入上一个状态中,即上一个yield表达式的结果。 在介绍Generator函数的异步时,先简单介绍一下Generator函数的几个方法
##1.next()方法:生成器函数里面的yield表达式并无值,或者说总返回undefined,next()函数能够接受一个参数,该参数就会被看成yield表达式的值。
##2.throw()方法:在函数体外抛出一个错误,而后在函数体内捕获。例如
function *gen1(){
try{
yield;
}catch(e){
console.log('内部捕获')
}
}
let g1 = gen1();
g1.next();
g1.throw(new Error());
复制代码
打印出
内部捕获
复制代码
##3.return()方法:返回给定值,并终结生成器。例如
function *gen2(){
yield 1;
yield 2;
yield 3;
}
let g2 = gen1();
g2.next();//{value:1,done:false}
g2.return();//{value:undefined,done:true}
g2.next();//{value:undefined.done:true}
复制代码
##4.yield*表达式:在生成器函数中调用另外一个生成器函数。例如
function *gen3(){
yield 1;
yield 2;
yield 3;
}
function *gen4(){
yield 4;
yield * gen3();
yield 5;
}
//等价于
function *gen4(){
yield 4;
yield 1;
yield 2;
yield 3;
yield 5;
}
复制代码
在使用Generator(生成器)函数作异步时,先引入协程这个概念,能够理解为 "协做的函数",一个协程本质就是子函数,不过这个子函数能够执行到一半,能够暂停执行,将执行权交给其余子函数,等稍后回收执行权的时候,还能够继续执行,跟线程很是像,在c++/python/java中一个线程的单位也是一个子函数(java的run方法),线程之间的切换,就至关于函数的切换,不过这个切换代价很是大,得保存不少跟线程相关东西,而协程则没那么复杂,因此协程又被称为纤程,或轻量级线程。
协程的执行流程大体以下:
##1.协程A开始执行。
##2.协程A执行到一半,进入暂停,执行权转移给协程B。
##3.(一段时间后)协程B交还执行权。
##4.协程A恢复执行
其中协程A就是异步任务,由于其分多段执行。
接下来将介绍使用Generator函数来实现协程,并作到异步。 首先来看一个简单的例子
const fs = require('fs');
function* gen(){//生成器函数
let data = yield asyncReadFile(__dirname+'/ajax_promise.js');
console.log(data); //文件读取成功 则输出
let data2 = yield timer(1000);
console.log(data2); //过1s后输出 hello world
}
let it = gen();
it.next();
function timer(time){//异步任务
setTimeout(() => it.next('hello world'),time)
}
function asyncReadFile(url) {//异步任务 读取文件
fs.readFile(url,(err,data) => {
it.next(data.toString())
})
}
复制代码
能够看出经过暂缓it.next()方法的执行,来实现异步的功能,若是仅看gen的函数里面内部,好比 let data = yield asyncReadFile(__dirname+'/ajax_promise.js');
这一段,能够理解为data等待异步读取文件asyncReadFile的结果,若是有告终果,则输出,gen继续向下执行,不过每个异步函数,好比asyncReadFile的实现却变麻烦了,这个时候就要借助Promise对象,例子以下
const promisify = require('./promisify');
function timer(time,callback){
setTimeout(() => callback(), time)
}
const asyncReadFile = promisify(require('fs').readFile);//借用以前的promisify方法,将callback形式转换为Promise
const asyncTimer = promisify(timer);
function *gen(){
let [data] = yield asyncReadFile('./a.mjs');//生成一个Promise对象
console.log(data);
yield asyncTimer(1000);
console.log('hello world');
}
let g = gen();
let {value} = g.next(); //{value:asyncReadFile('./a.mjs'),done:false}
value.then(data => {//至关于asyncReadFile('./a.mjs').then(data => {})
let {value} = g.next(data);//{value:asyncTimer(1000),done:false}
value.then(data => {//至关于asyncTimer(1000).then(data => {})
g.next(data);//{value:undefined,done:true}
})
});
复制代码
能够看出上面的借助Promise对象例子,在异步处理上能够有更通用的实现,即生成器执行器,
//run.js
function run(gen){//传入一个生成器函数
let g = gen();
function next(data){
let result = g.next(data);
let {value,done} = result;
if(done) return value;//done为true时结束递归
if (Array.isArray(value)) value = Promise.all(value);//若是yield表达式后面跟的是一个数组,能够将其转换为Promise.all
if(!value instanceof Promise) value = Promise.resolve(value)//不是Promise对象,则转成Promise对象
value.then((data) => {
next(data);//递归调用
});
}
next();//启动生成器
}
复制代码
借助run执行器函数,运行上面的gen只须要run(gen)便可 最后让咱们来继续改写以前的ajax例子,此次使用Generator函数,代码以下
//test.html
ajax = promisify(ajax);
ajax.baseUrl = 'http://localhost:3000';
ajax.get = (url,data) => ajax({url: url, method: 'GET', data: data});
ajax.post = (url,data) => ajax({url: url, method: 'POST', data: data});
run(function*(){
let [[data1],[data2]] = yield [ajax.get('/api/test1', {name: 'dpf', age: 19}),ajax.post('/api/test2', {name: 'youname', age: 19})];//至关于Promise.all
console.log(data1,data2)
});
复制代码
使用Generator函数无疑是解决异步的优于callback(回调),及Promise对象的好方法,没有callback回调地狱,Promise对象的过长then链,异步代码看起来跟同步代码同样,可读性,和维护性都较好。
5.async/await(javascript异步的终极解决方案)
es6中使用Generator函数来作异步,在ES2017中,提供了async/await两个关键字来实现异步,让异步变得更加方便。 async/await本质上仍是基于Generator函数,能够说是Generator函数的语法糖,async就至关于以前写的run函数(执行Generator函数的函数),而await就至关于yield,只不过await表达式后面只能跟着Promise对象,若是不是Promise对象的话,会经过Promise.resolve方法使之变成Promise对象。async修饰function,其返回一个Promise对象。await必须放在async修饰的函数里面,就至关于yield只能放在Generator生成器函数里同样。一个简单的例子
//封装一个定时器,返回一个Promise对象
const timer = time => new Promise((resolve,reject) => {
setTimeout(() => resolve('hello world'),time)
});
async function main() {//async函数
let start = Date.now();
let data = await timer(1000);//能够把await理解为 async wait 即异步等待(虽然是yield的变体),当Promise对象有值的时候将值返回,即Promise对象里resolve(data)里面的data,做为await表达式的结果
console.log(data,'time = ',Date.now() - start,'ms')//将会输出 hello world time = 1002 ms
}
main();
复制代码
能够看到async/await使用起来很是方便,其实async/await的原理也很是简单,就是把Generator函数和执行器包装在一块儿,其实现以下
//spawn.js
//以前的run函数的变体,只不过多了错误处理,而后返回的是Promise对象
function spawn(genF){
return new Promise((resolve,reject) => {
let g = genf();
function next(nextF){
let next;
try{
next = nextF();
}catch(e){
reject(e)
}
if(next.done) return resolve(next.value);
Promise.resolve(next.value)
.then(data => next(() => g.next(data)))
.catch(err => next(() => g.throw(err)));
}
next(() => g.next(undefined))
})
}
复制代码
因此以前的async function main() {} 就等价于 function main() { return spawn(function *() {}) },了解async的内部原理能够有助于理解和使用async。
接下来看使用async/await来改进以前的ajax的例子
//test.html
ajax = promisify(ajax);
ajax.baseUrl = 'http://localhost:3000';
ajax.get = (url,data) => ajax({url: url, method: 'GET', data: data});
ajax.post = (url,data) => ajax({url: url, method: 'POST', data: data});
(async function() {
let [data1,data2] = await Promise.all([ajax.get('/api/test1', {name: 'dpf', age: 19}),ajax.post('/api/test2', {name: 'youname', age: 19})]);
console.log(data1,data2)
})()
复制代码
到此,这篇文章已经接近尾声,总结一下JavaScript实现异步的这五种方式的优缺点 ##1.callback(回调函数):写起来方便,不过过多的回调会产生回调地狱,代码横向扩展,不易于维护和理解
##2.发布订阅模式:经过实现个事件管理器,方便管理和修改事件,不一样的事件对应不一样的回调,通触发事件来实现异步,不过会产生一些命名冲突的问题,事件处处触发,可能代码可读性很差。
##3.Promise对象:本质是用来解决回调产生的代码横向扩展,及可读性不强的问题,经过.then方法来替代掉回调,并且then方法接的参数也有限制,因此解决了,回调产生的参数不容易肯定的问题,缺点的话,我的以为,写起来可能不那么容易,不过写好了,用起来就就方便多了。
##4.Generator(生成器)函数:记得第一次接触Generator函数是在python中,并且协程的概念,以及使用生成器函数来实现异步,也是在python中学到的,感受javascript有点是借鉴到python语言中的,不过确实很好的解决了JavaScript中异步的问题,不过得依赖执行器函数。
##5.async/await:这种方式多是javascript中,解决异步的最好的方式了,让异步代码写起来跟同步代码同样,可读性和维护性都上来了。
最后文章中的全部代码,均在个人github上 github.com/sundial-dre…
,但愿这篇文章能让你对JavaScript异步有必定的认识。