1.什么是同步?
同步就是发出一个调用,在没有得到结果前,该调用不会返回,但是该调用一旦返回,就得到了程序执行的返回值,就是说调用者在调用时会主动等待调用执行的结束返回的结果,在该调用执行完毕之前会阻塞后边代码的执行。
2.什么是异步?
"调用"在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在"调用"发出后,"被调用者"通过状态、通知来通知调用者,或通过回调函数处理这个调用。异步调用发出后,不影响后面代码的执行。
3.js异步出现的原因
javascript是单线程的,同步代码的执行会阻塞之后代码的执行,当我们遇到一个任务需要较长等待时间时,同步则会阻塞后边代码的执行,必须要等带这个任务的完成才能执行接下来的操作,必然会会降低效率。而异步代码则不会,不会等待异步代码完成时,才会执行接下里的代码,而是直接执行下面的代码,等到异步任务完成时,产生一个回调,通知线程来执行相应的操作。
4.异步发展史
异步最早的解决方案是回调函数,如事件的回调,setInterval/setTimeout中的回调。但是回调函数有一个很常见的问题,就是回调地狱的问题(稍后会举例说明);
为了解决回调地狱的问题,社区提出了Promise解决方案,ES6将其写进了语言标准。Promise一定程度上解决了回调地狱的问题,但是Promise也存在一些问题,如错误不能被try catch,而且使用Promise的链式调用,其实并没有从根本上解决回调地狱的问题,只是换了一种写法。
ES6中引入 Generator 函数,Generator是一种异步编程解决方案,Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权,Generator 函数可以看出是异步任务的容器,需要暂停的地方,都用yield语句注明。但是 Generator 使用起来较为复杂。
ES7又提出了新的异步解决方案:async/await,async是 Generator 函数的语法糖,async/await 使得异步代码看起来像同步代码,异步编程发展的目标就是让异步逻辑的代码看起来像同步一样。
回调函数 ---> Promise ---> Generator ---> async/await.
callback :
比如文件的读取就是基于callback封装的
//node读取文件 fs.readFile(xxx, 'utf-8', function(err, data) { //code });
例子:
function f1(str,callback) {
// body...
console.log(str);
callback("回调callback");
}
f1("执行回调",function(result){
console.log(result)
});
回调函数的优点: 简单。
回调函数的缺点:
异步回调嵌套会导致代码难以维护,并且不方便统一处理错误,不能 try catch
和 回调地狱(如先读取A文本内容,再根据A文本内容读取B再根据B的内容读取C...)。
这样就形成了回调地狱
2.1promise基本语法
var fs = require('fs');
//1.给别人一个承诺,一旦promise被创建就开始执行里边的代码
var p1 = new Promise(function (resolve,reject) {
// body...
fs.readFile('./data/aa.txt','utf8',function (err,data) {
// body...
if (err) {
//失败了承诺中的任务就失败了,并且把Pending状态改成Recjected
reject(err)
}else{
//成功了承诺中的任务就成功了,并且把Pending状态改成Resolved
resolve(data)
}
})
})
//p1就是那个承诺,p1成功了,then会做指定的操作,第一个function
//接收的时reslove函数;第二个function 接收的时reject函数
p1.then(function(data){
console.log(data)
},function(err){
console.log(err)
})
2.2多个promise函数使用。链式使用
var fs = require('fs')
var p1 = new Promise(function (resolve, reject) {
fs.readFile('./data/a.txt', 'utf8', function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
var p2 = new Promise(function (resolve, reject) {
fs.readFile('./data/b.txt', 'utf8', function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
var p3 = new Promise(function (resolve, reject) {
fs.readFile('./data/c.txt', 'utf8', function (err, data) {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
p1
.then(function (data) {
console.log(data)
// 当 p1 读取成功的时候
// 当前函数中 return 的结果就可以在后面的 then 中 function 接收到
// 当你 return 123 后面就接收到 123
// return 'hello' 后面就接收到 'hello'
// 没有 return 后面收到的就是 undefined
// 上面那些 return 的数据没什么卵用
// 真正有用的是:我们可以 return 一个 Promise 对象
// 当 return 一个 Promise 对象的时候,后续的 then 中的 方法的第一个参数会作为 p2 的 resolve
//
return p2
}, function (err) {
console.log('读取文件失败了', err)
})
.then(function (data) {
console.log(data)
return p3
})
.then(function (data) {
console.log(data)
console.log('end')
})
promise链式调用
2.3 使用promise封装异步的读取文件操作
var fs = require('fs');
//1.给别人一个承诺,一旦promise被创建就开始执行里边的代码
function readData(filepath){
return new Promise(function (resolve,reject) {
// body...
fs.readFile(filepath,'utf8',function (err,data) {
// body...
if (err) {
//失败了承诺中的任务就失败了,并且把Pending状态改成Recjected
reject(err)
}else{
//成功了承诺中的任务就成功了,并且把Pending状态改成Resolved
resolve(data)
}
})
})
}
readData('./data/a.txt')
.then(function(data){
console.log(data)
return readData('./data/b.txt')
}).then(function(data){
console.log(data)
return readData('./data/c.txt')
}).then(function(data){
console.log(data)
})
3.Generator
Generator 函数是 ES6 提供的一种异步编程解决方案,整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield 语句注明。
Generator 函数一般配合 yield 或 Promise 使用。Generator函数返回的是迭代器。对生成器和迭代器不了解的同学,请自行补习下基础。下面我们看一下 Generator 的简单使用:
function* gen() {
let a = yield 111;
console.log(a);
let b = yield 222;
console.log(b);
let c = yield 333;
console.log(c);
let d = yield 444;
console.log(d);
}
let t = gen();
//next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
t.next(1); //第一次调用next函数时,传递的参数无效
t.next(2); //a输出2;
t.next(3); //b输出3;
t.next(4); //c输出4;
t.next(5); //d输出5;
复制代码
为了让大家更好的理解上面代码是如何执行的,我画了一张图,分别对应每一次的next方法调用:
仍然以上文的 readFile (先读取A文本内容,再根据A文本内容读取B再根据B的内容读取C)为例,使用 Generator + co库来实现:
const fs = require('fs');
const co = require('co');
const bluebird = require('bluebird');
const readFile = bluebird.promisify(fs.readFile);
function* read() {
yield readFile(A, 'utf-8');
yield readFile(B, 'utf-8');
yield readFile(C, 'utf-8');
//....
}
co(read()).then(data => {
//code
}).catch(err => {
//code
});
4.
ES7中引入了 async/await 概念。async 其实是一个语法糖,它的实现就是将 Generator函数和自动执行器(co),包装在一个函数中。
async/await 的优点是代码清晰,不用像 Promise 写很多 then 链,就可以处理回调地狱的问题。并且错误可以被try catch。
async是什么?
async是Generator的的语法糖
先来看Generator的语法规则与用法
var fs = require('fs');
var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) reject(error);
resolve(data);
});
});
};
var gen = function* (){
var f1 = yield readFile('./a.txt');
var f2 = yield readFile('./b.txt');
console.log(f1.toString());
console.log(f2.toString());
};
转换成标准的promise链式调用:
readFile('./a.txt').then((res)=>{
console.log(res.toString());
return readFile('./b.txt');
}).then((res)=>{
console.log(res.toString());
})
换成async/await格式:
var asyncReadFile = async function (){
var f1 = await readFile('./a.txt');
var f2 = await readFile('./b.txt');
console.log(f1.toString());
console.log(f2.toString());
};
asyncReadFile();
一比较就会发现,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。
async 函数对 Generator 函数的改进,体现在以下三点。
(1)内置执行器。 Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。
var result = asyncReadFile();
(2)更好的语义。 async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
(3)更广的适用性。 co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)
同 Generator 函数一样,async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。
//测试到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句,但不会阻止函数体外的后面的语句
//await 会让出线程
async function asyncSort(){
var f1 = await readFile('./a.txt');
var f2 = await readFile('./b.txt');
return f2.toString();
}
asyncSort().then((res)=>{
console.log(res);
})
console.log(111)
下面的例子指定多少毫秒输出一个值
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value)
}
asyncPrint('hello world', 2000);
两秒后出现hello word ;这说明 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。
注意点:
(1)
await 命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中。
async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); } } // 另一种写法 async function myFunction() { await somethingThatReturnsAPromise().catch(function (err){ console.log(err); }); }
(2)
await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。
async function dbFuc(db) { let docs = [{}, {}, {}]; // 报错 docs.forEach(function (doc) { await db.post(doc); }); }
(3)如果希望多个请求并发执行,可以使用 Promise.all 方法。
使用 async/await 实现此需求:读取A,B,C三个文件内容,都读取成功后,再输出最终的结果。
function read(url) {
return new Promise((resolve, reject) => {
fs.readFile(url, 'utf8', (err, data) => {
if(err) reject(err);
resolve(data);
});
});
}
async function readAsync() {
let data = await Promise.all([
read(A),
read(B),
read(C)
]);
return data;
}
readAsync().then(data => {
console.log(data);
});
总结:JS的异步发展史,可以认为是从 callback -> promise -> generator -> async/await。async/await 使得异步代码看起来像同步代码,异步编程发展的目标就是让异步编程看起来像同步一样。异步编程的最高境界就是不用关心他是不是异步,因此async/await被很多人为是异步编程的终极解决方案。
下面给出几个测试题:可以思考一下异步编程的思想:
例1
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
promise2
async1 end
setTimeout */
例2:
function testSometing() {
console.log("执行testSometing");
return "testSometing";
}
async function testAsync() {
console.log("执行testAsync");
return Promise.resolve("hello async");
}
async function test() {
console.log("test start...");
const v1 = await testSometing();//关键点1
console.log(v1);
const v2 = await testAsync();
console.log(v2);
console.log(v1, v2);
}
test();
var promise = new Promise((resolve)=> { console.log("promise start.."); resolve("promise");});//关键点2
promise.then((val)=> console.log(val));
console.log("test end...")
// await异步是让出线程
/**test start...
执行testSometing
promise start..
test end...
testSometing
执行testAsync
promise
hello async
testSometing hello async */