js异步发展史

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 */
 

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值