异步的来源
js是单线程的语言,所谓单线程即代码一行一行的执行,后面的代码必须等待前面的执行完毕才可以执行,对于普通的耗时短的代码来说可能没有什么问题,但是对于耗时长的一段代码来说,就会造成卡顿,比如说发起一个网络请求,请求的资源何时返回,这个时间是不可预估的,这种情况下会造成卡顿,所以js针对这种情况设计了异步。
处理异步的几种方式
回调函数
var ajax = $.ajax({
url: '/a/b',
success: function () {
console.log('success')
}
})
回调函数最常见的就是在ajax请求中,以上请求中传入两个参数,url和success,url是请求的路径,success是一个函数,success不会立马执行,而是等待请求成功之后才执行,这种就称为回调函数。
原理:
将回调函数作为参数传递给异步执行的函数,当有结果返回之后再触发回调函数。
弊端:
多个回调函数嵌套,会造成回调地狱现象,非常难以阅读和维护。
promise
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
promise.then(function(value) {
// success
}, function(error) {
// failure
});
Promise对象是一个构造函数,用来生成promise实例,Promise接受一个函数作为参数,该函数有两个参数,resolve和reject,resolve的作用是将状态由未完成变为成功,reject是将状态变为失败。Promise实例生成之后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
原理:
每一个异步任务返回一个promise对象,该对象有一个then方法,允许指定回调函数。
优点:
回调函数写成了链式写法,程序的流程可以看得很清楚,而且有一整套的配套方法。
Generator
在讲Generator函数的异步应用之前必须要先弄清楚迭代器和生成器的概念
iterator基本概念
iterator即迭代器,它是一种接口,为各种数据结构提供统一的访问机制,只要数据结构部署iterator接口,就可以实现遍历。
iterator是一个对象,它知道如何去访问集合中的一项,并且跟踪该序列中的当前位置,它提供一个next方法,该方法返回包含了value和done两个属性的对象, value 是当前成员的值,done是一个布尔值,表示遍历是否结束。
function iterator(array){
var nextIndex = 0;
return {
next: function(){
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{done: true};
}
};
}
var it = iterator(['yo', 'ya']);
console.log(it.next()); // {value: 'yo', done: false}
console.log(it.next()); // {value: 'ya', done: false}
console.log(it.next()); // {value: undefined, done: true}
一个数据结构只要具有Symbol.iterator属性就是可迭代的(不管是本身还是原型链上有该属性),Symbol.iterator本身是一个函数,是当前数据结构的遍历器生成函数,当数据需要被迭代时,就会被调用然后返回一个用于在迭代中获得值的迭代器。
iterator调用场合
- 解构赋值
- 扩展运算符
- yield*
- 其他场合
for of
Array.from
promise.all()
promise.race()
Map()
Set()
参考: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Iterators_and_generators
GeneratorFunction
由于自定义迭代器需要显式地维护其内部状态,比较繁琐,所以提供了GeneratorFunction(生成器函数)。
GeneratorFunction是一个可以作为迭代器工厂的特殊函数。当它被执行时会返回一个新的Generator对象。 如果使用 function*语法,则函数会变成GeneratorFunction。
//idMaker 是一个GeneratorFunction
function* idMaker() {
var index = 0;
while(true)
yield index++;
}
生成器函数在执行时能暂停,后面又能从暂停处继续执行。
调用一个生成器函数,并不会马上执行内部的语句,而是会返回这个生成器的迭代器对象,当调用该迭代器对象的next方法时会执行内部语句,当遇到yield时就会暂停执行,yield后面紧跟该迭代器要返回的值,如果用的yield* ,则表示将执行权移交给另一个生成器函数,当前生成器暂停执行。
next()方法返回一个包含value和done两属性的对象,value 属性表示本次 yield 表达式的返回值,done 属性为布尔类型,表示生成器后续是否还有 yield 语句,即生成器函数是否已经执行完毕并返回。调用 next()方法时,如果传入了参数,那么这个参数会作为上一条执行的 yield 语句的返回值。
function* generator(i) {
yield i;
yield 'hello';
y = yield 'world';
yield y;
return 'ending';
}
var hw = generator(3); // // "Generator { }"
hw.next(); // { value:3, done: false }
hw.next(); // { value: 'hello', done: false }
hw.next(); // { value: 'world', done: false }
hw.next(2); // { value: 2, done: false }
hw.next(); // { value: 'ending', done: true }
hw.next(); // { value: undefined, done: true }
当在生成器函数中显式 return 时,会导致生成器立即变为完成状态,即调用 next() 方法返回的对象的 done 为 true。如果 return 后面跟了一个值,那么这个值会作为当前调用 next() 方法返回的 value 值,如果没有则为undefined。
function* generator() {
yield i;
yield 'hello';
return 2;
y = yield 'world';
yield y;
return 'ending';
}
var hw = generator(); // "Generator { }"
hw.next(); // { value: 'hello', done: false }
hw.next(); // { value: 2, done: true }
hw.next(); // { value: undefined, done: true }
Generator函数的异步应用
Generator 函数是协程在 ES6 的实现,协程的意思是多个线程互相协作,完成异步任务,最大特点就是可以交出函数的执行权即暂停执行。
整个 Generator 函数就是一个封装的异步任务,需要暂停的地方,都用yield语句注明
var fetch = require('node-fetch');
function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
}
var g = gen();
var result = g.next(); // result.value是一个promise
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
});
弊端:流程管理不方便,手动调用next方法
Generator 函数的自动流程管理
针对Generator 函数的流程管理不方便的弊端,提出了以下几个方法:
- 回调函数。 将异步操作包装成Thunk函数,在回调函数里面交回执行权
前提:每一个异步操作都是都是Thunk函数,即yield命令之后的都要是Thunk函数。
var fs = require('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile); //转换为Thunk函数
// 生成器函数
var g = function* (){
var f1 = yield readFileThunk('fileA');
var f2 = yield readFileThunk('fileB');
// ...
var fn = yield readFileThunk('fileN');
};
// 自动执行next
function run(fn) {
var gen = fn();
function next(err, data) {
var result = gen.next(data);
if (result.done) return;
result.value(next);
}
next();
}
run(g);
- Promise对象,将异步操作包装成 Promise 对象,用then方法交回执行权
var fs = require('fs');
// 将异步操作包装成 Promise 对象
var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) return reject(error);
resolve(data);
});
});
};
// 生成器函数
var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
//用then方法交回执行权,调用next方法
function run(gen){
var g = gen();
function next(data){
var result = g.next(data);
if (result.done) return result.value;
result.value.then(function(data){
next(data);
});
}
next();
}
run(gen);
- co模块
co模块就是将Thunk函数和Promise对象包装成一个模块,使用co模块的前提是yield之后只能是Thunk函数或者Promise对象,
var co = require('co');
// 生成器函数
var gen = function* () {
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
// co函数返回一个Promise对象,因此可以用then方法添加回调函数
co(gen).then(function (){
console.log('Generator 函数执行完成');
});
参考:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/function*
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/yield
async await
async函数是Generator函数的语法糖,他们的区别在于:
- async函数自带内置执行器
Generator函数的执行必须要靠执行器,所以有了co模块,但是async函数自带执行器。 - async函数适用性更广
使用co模块时,yield后面必须跟Thunk函数或者Promise对象,但是async函数后面可以是 Promise 对象和原始类型的值(跟原始类型值时相当于同步操作)。 - 返回值不同
async函数返回值是Promise对象,Generator函数返回值是Iterator对象。
实现原理:将 Generator 函数和自动执行器,包装在一个函数里
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);
});
});
};
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
async函数内部return语句返回的值,会成为then方法回调函数的参数, 抛出的错误对象会被catch方法回调函数接收到。
async function f() {
await Promise.reject('出错了');
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了
参考:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function