终于意识到要写博客的第一天
我终于意识到写博客的重要性,哎,现在开始也不太晚吧,总比不开始强
今天主要弄明白以下几个问题吧
- 1、关于promise、aysnc/await、Generator/yield的使用以及原理
- 2、关于vue的生命周期、插槽、computed/watch原理、数据绑定原理、vue打包过程等几个常见的vue问题
- 3、web前端安全,XSS、CRSF攻击及其防御手段
1 promise的实现、使用以及注意事项
1.1 promise简介
promise本质上是一个构造函数,用new关键词来创建,静态方法有all/race/reject/resolve,原型方法有then/catch.
var p =new Promise((resolve, reject)=>{
//异步操作
setTimeout(
function(){console.log(‘done’);resolve(‘done’)},2000);
});
Promise构造函数接收一个参数,是函数,该函数有两个参数,resolve和reject,分别表示异步操作执行成功后的回调和失败后的回调。
关于promise注意的几个问题:
- promise一般用来实现一件事件做完之后,再做另一件事情(也可以用回调函数,promise 的提出正是为了解决回调地狱问题)
- 异步操作有三种状态:pending等待、fulfilled已完成、rejected已拒绝,resolve是将Promise的状态置为fullfiled,reject是将Promise的状态置为rejected,构造函数中的 resolve 或 reject 只有第一次执行有效,多次调用没有任何 作用。promise 状态一旦改变则不能再变。
- then方法是实现promise 的核心,promise 的 .then 或者 .catch 可以被调用多次,但这里 Promise 构造函数只执行一次。或者说 promise 内部状态一经改变,并且有了一个值,那么后续每次调用 .then 或者 .catch 都会直接拿到该值
- new一个promise之后就会立即执行,所以我们用的时候一般会将promise包在一个函数中,需要的时候再去运行函数,并且在成功的回调中return这个函数,也就是return了一个promise对象
- 注意promise构造函数中代码的执行顺序,promise的构造函数是同步执行,promise.then中的函数是异步执行
- 第一个then不管是成功的回调还是失败的回调,只要返回一个普通值(不抛出错误或者返回promise),都会执行下一个then的成功的回调-
1.2 promise 实现
首先明确ES6为Promise提供了以下API
(1)构造函数
function Promise(handler){}
(2)原型方法
Promise.prototype.then=function(){}
Promise.prototype.catch=function(){}
Promise.prototype.finally=function(){}
(3)静态方法
Promise.resolve=function(){}
Promise.reject=function(){}
Promise.all=function(){}
Promise.race=function(){}
1.2.1 构造函数的简单实现
function Promise(executor) {
var self = this;
self.status = 'pending'; //promise当前的状态
self.data = undefined; //promise的值
self.onResolvedCallback = [];
//promise状态变为resolve时的回调函数集,可能有多个
self.onRejectedCallback = [];
//promise状态变为reject时的回调函数集,可能有多个
function resolve(value) {
setTimeout(function () {
if(self.status === 'pending') {
self.status = 'resolved';
self.data = value;
for(var i = 0; i < self.onResolvedCallback.length; i++) {
self.onResolvedCallback[i](value);
}
}
})
}
function reject(reason) {
setTimeout(function () {
if(self.status === 'pending') {
self.status = 'rejected';
self.data = reason;
for(var i = 0; i < self.onRejectedCallback.length; i++) {
self.onRejectedCallback[i](reason);
}
}
})
}
try {
executor(resolve, reject);
} catch (e){
reject(e);
}
};
1.2.2 then方法
Promise/A+规范中规定then方法需要返回一个新的promise对象
(1) then方法返回一个新的promise对象。
(2) executor自执行函数中的resolve参数调用时执行then方法的第一个回调函数onResolved。
(3) executor自执行函数中的reject参数调用时执行then方法的第二个回调函数onRejected。
Promise.prototype.then=function(onResolved, onRejected){
var self=this;
var promise2;
onResolved=typeof onResolved === 'function' ? onResolved : function(value){ return value;}
onRejected=typeof onRejected === 'function' ? onRejected : function(reason){return reason;}
//promise对象当前状态为resolved
if(self.status === 'resolved') {
return promise2 = new Promise(function (resolve, reject) {
try {
//调用onResolve回调函数
var x = onResolved(self.data);
//如果onResolve回调函数返回值为一个promise对象
if(x instanceof Promise) {
//将它的结果作为promise2的结果
x.then(resolve, reject);
} else {
resolve(x);//执行promise2的onResolve回调
}
} catch (e) {
reject(e); //执行promise2的onReject回调
}
})
}
//promise对象当前状态为rejected
if(self.status === 'rejected') {
return promise2 = new Promise(function (resolve, reject) {
try {
var x = onRejected(self.data);
if (x instanceof Promise) {
x.then(resolve, reject)
} else {
resolve(x)
}
} catch (e) {
reject(e)
}
})
}
//promise对象当前状态为pending
//此时并不能确定调用onResolved还是onRejected,需要等当前Promise状态确定。
//所以需要将callBack放入promise1的回调数组中
if(self.status === 'pending') {
return promise2 = new Promise(function (resolve, reject) {
self.onResolvedCallback.push(function (value) {
try {
var x = onResolved(self.data);
if (x instanceof Promise) {
x.then(resolve, reject);
} else {
resolve(x);
}
} catch (e) {
reject(e);
}
})
self.onRejectedCallback.push(function(reason) {
try {
var x = onRejected(self.data);
if (x instanceof Promise) {
x.then(resolve, reject)
} else {
resolve(x);
}
} catch (e) {
reject(e)
}
})
})
}
}
1.3 promise实现ajax
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
1.4 promise的then方法使用
采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function (comments) {
console.log("resolved: ", comments);
}, function (err){
console.log("rejected: ", err);
});
1.5 promise的catch用法
Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
});
上面代码中,getJSON方法返回一个 Promise 对象,如果该对象状态变为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
1.6 promise的finally用法
promise的finally指定不管promise对象最后的状态如何,都会执行的操作。该方法是ES2018引入的。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
1.7 promise的all/race用法
1.7.1 Promise.all()
Promise.all()方法用于将多个promise实例包装成一个新的promise实例
const p=Promise.all([p1,p2,p3]);
接受一个数组作为参数,p1,p2,p3都是Promise的实例,如果不是,就用Promise.reslove静态方法将其转换。p的状态由三个promise实例决定。
- 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
- 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数
// 生成一个Promise对象的数组
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
1.7.2 Promise.race()
Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log)
.catch(console.error);
上面代码中,如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数
1.8 Promise的resolve和reject
1.8.1 Promise.resolve()
有时需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用。
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
Promise.resolve等价于下面的写法。
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo')
1.8.2 Promise.reject()
Promise.reject(reason)返回一个新的promise实例,该实例的状态为rejected。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
2 Generator函数及其异步应用
2.1 Generator/yield的基本概念
语法上,可以把理解成,Generator 函数是一个状态机,封装了多个内部状态。
形式上,Generator 函数是一个普通函数。
整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器,异步操作需要暂停的地方,都用yield语句。
Generator函数特征:
(1)function 关键字和函数之间有一个星号(*),且内部使用yield表达式,定义不同的内部状态。
(2)调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。
(3)必须调用遍历器对象的next方法,使得指针移向下一个状态。每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止.
总之,Generator函数是分段执行的,yield是暂停执行的标记,next方法恢复执行
yield关键字
由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志
(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined
next方法
yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
上面代码中,第二次运行next方法的时候不带参数,导致 y 的值等于2 * undefined(即NaN),除以 3 以后还是NaN,因此返回对象的value属性也等于NaN。第三次运行Next方法的时候不带参数,所以z等于undefined,返回对象的value属性等于5 + NaN + undefined,即NaN。
如果向next方法提供参数,返回结果就完全不一样了。上面代码第一次调用b的next方法时,返回x+1的值6;第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y / 3的值8;第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42
例如:
function
* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
next()方法返回的对象上的value属性就是紧跟yeild表达式的值
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
2.2 for…of循环
使用for…of循环可以自动遍历Generator函数运行时生成的Iterator对象,且此时不需要调用next方法
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
上面代码使用for…of循环,依次显示 5 个yield表达式的值。这里需要注意,一旦next方法的返回对象的done属性为true,for…of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for…of循环之中
2.3应用示例
1、输出斐波那契数列
function* fibonacci(){
let [pre,cur]=[0,1];
for(;;){
[pre,cur]=[cur,pre+cur]
yield cur;
}
}
for(let n of fibonacci()){
if(n>1000)
break;
console.log(n);
}
2、 Genarator 逐行读取文本文件
function* readFileByLine(){
let file = new FileReader("a.txt");
try{
while(!file.eof){
yield parseInt(file.readLine(), 10); // 使用yield表达式可以手动逐行读取文件
}
}finally{
file.close();
}
}
var r = readFileByLine();
r.next();
3、Generator与状态机
如果我们想实现一个功能,函数每次执行一次就改变一次状态,要怎么实现?如果你使用es5实现你可能会这样做
var flag = true;
var clock = function() {
if (flag)
console.log('Tick!');
else
console.log('Tock!');
flag = !flag;
}
使用Generator实现
function* clock(){
while(true){
console.log('Tick!');
yield;
console.log('Tock');
yield
}
}
const gen = clock();
gen.next(); // Tick!
gen.next(); // Tock!
gen.next(); // Tick!
gen.next(); // Tock!
2.4 Generator函数应用
ES6 诞生以前,异步编程的方法,大概有下面四种。
- 回调函数
- 事件监听
- 发布/订阅
- Promise 对象
Generator函数可以利用yield关键字暂停函数执行,利用这一特性,我们可以将耗时的异步操作放在yield关键字之后,这样等到调用next()方法的时候再继续往后执行,这样实际上就省略了回调函数
function apiInterface(url){
ajaxCall(url,function(res){
it.next(res);
});
}
function* getInfo(){
var result=yield apiInterface("http://yuo");
var resp=JSON.parse(result);
console.log(resp.value);
}
var it=getInfo();
it.next();
这里定义了一个Generator函数getInfo,yield关键字后面跟着一个调用ajax方法获取信息的方法,调用it.next()开始执行apiInerface方法,apiInerface方法里面又执行了一次next(),并同时将ajax请求的返回值response作为参数传入next(),注意,这个res参数一定要传,否则result值为undefined,这里要记住一句话:next方法参数的作用,是覆盖掉上一个yield语句的值。
如果现在有三个比较耗时的操作step1,step2,step3,现在想先执行step1,然后将执行step1之后得到的值value1作为参数传入step2,再将执行step2之后得到的结果value2作为参数执行step3,如果使用回调的方式可以这样编写代码
step1(function(value1){
step2(function(value1,function(value2){
step3(value2,function(value3){
//do sth;
}
}
}
如果将上面的需求用Promise方法改写可以这么写:
step1().then(function(value1) {
return step2(value1);
}).then(function(value2){
return step3(value2);
}) .then(function(value3) {
// do something with value3
});
用Promise的写法可以很好的避免出现回调函数的情况,注意,这里我们假设step1,step2,step3是三个Promise对象。
最后我们看下使用Generator函数如何改造上述需求:
function* someTask(value1){
try{
var value2=yield step1(value1);
var value3=yield step2(value2);
var value4=yield step3(value3);
// do sth
}catch(e){}
}
然后定义一个调度函数一次执行三个任务:
scheduler(someTask(initialValue));
function scheduler(task) {
var taskObj = task.next(task.value);
// 如果Generator函数未结束,就继续调用
if (!taskObj.done) {
task.value = taskObj.value
scheduler(task);
}
}
通过Generator函数将回调方式改成了线性方式,但是上述方式只适合同步操作,因为这里的代码在得到上一步的结果之后就会立即执行,没有判断异步操作合适结束。
这时,Thunk 函数就能派上用处。以读取文件为例。下面的 Generator 函数封装了两个异步操作。
var fs = require('fs');
var thunkify = require('thunkify');
var readFileThunk = thunkify(fs.readFile);
var gen = function* (){
var r1 = yield readFileThunk('/etc/fstab');
console.log(r1.toString());
var r2 = yield readFileThunk('/etc/shells');
console.log(r2.toString());
};
上面代码中,yield命令用于将程序的执行权移出 Generator 函数,那么就需要一种方法,将执行权再交还给 Generator 函数。
这种方法就是 Thunk 函数,因为它可以在回调函数里,将执行权交还给 Generator 函数。为了便于理解,我们先看如何手动执行上面这个 Generator 函数。
var g = gen();
var r1 = g.next();
r1.value(function (err, data) {
if (err) throw err;
var r2 = g.next(data);
r2.value(function (err, data) {
if (err) throw err;
g.next(data);
});
});
自动执行器:
var g = function* (){
var f1 = yield readFileThunk('fileA');
var f2 = yield readFileThunk('fileB');
// ...
var fn = yield readFileThunk('fileN');
};
run(g);
上面代码的run函数,就是一个 Generator 函数的自动执行器。内部的next函数就是 Thunk 的回调函数。next函数先将指针移到 Generator 函数的下一步(gen.next方法),然后判断 Generator 函数是否结束(result.done属性),如果没结束,就将next函数再传入 Thunk 函数(result.value属性),否则就直接退出
上面代码中,函数g封装了n个异步的读取文件操作,只要执行run函数,这些操作就会自动完成。这样一来,异步操作不仅可以写得像同步操作,而且一行代码就可以执行。
流程控制
协程可以理解成多线程间的协作,比如说A,B两个线程根据实际逻辑控制共同完成某个任务,A运行一段时间后,暂缓执行,交由B运行,B运行一段时间后,再交回A运行,直到运行任务完成。对于JavaScript单线程来说,我们可以理解为函数间的协作,由多个函数间相互配合完成某个任务。
下面我们利用饭店肚包鸡的制作过程来说明,熊大去饭店吃饭,点了只肚包鸡,然后就美滋滋的玩着游戏等着吃鸡。这时后厨就开始忙活了,后厨只有一名大厨,还有若干伙计,由于大厨很忙,无法兼顾整个制作过程,需要伙计协助,于是根据肚包鸡的制作过程做了如下的分工。
肚包鸡的过程:准备工作(宰鸡,洗鸡,刀工等)->炒鸡->炖鸡->上料->上桌
大厨很忙,负责核心的工序:炒鸡,上料
伙计负责没有技术含量,只有工作量的打杂工序:准备工作,炖鸡,上桌
//大厨的活
function* chef(){
console.log("fired chicken");//炒鸡
yield "worker";//交由伙计处理
console.log("sdd ingredients");//上料
yield "worker";//交由伙计处理
}
//伙计的活
function* worker(){
console.log("prepare chicken");//准备工作
yield "chef";//交由大厨处理
console.log("stewed chicken");
yield "chef";//交由大厨处理
console.log("serve chicken");//上菜
}
var ch = chef();
var wo = worker();
//流程控制
function run(gen){
var v = gen.next();
if(v.value =="chef"){
run(ch);
}else if(v.value =="worker"){
run(wo);
}
}
run(wo);//开始执行
我们来分析下代码,我们按照大厨和伙计的角色,分别创建了两个Generator函数,chef和worker。函数中列出了各自角色要干的活,当要转交给其他人任务时,利用yield,暂停执行,并将执行权交出;run方法实现流程控制,根据yield返回的值,决定移交给哪个角色函数。相互配合,直到完成整个过程,熊大终于可以吃上肚包鸡了。
prepare chicken
fired chicken
stewed chicken
sdd ingredients
serve chicken
异步扁平化
那什么是异步的流控呢,简单说就是按顺序控制异步操作,以上面的肚包鸡为例,每个工序都是可认为异步的过程,工序之间又是同步的控制(上一个工序完成后,才能继续下一个工序),这就是异步流控。
setTimeout(function(){
console.log('prepare chicken');
setTimeout(function(){
console.log('fired chicken');
...
},500)
} ,500)
用setTimeout方法来模拟异步过程,这种层层嵌套就是回调地狱,就是回调地狱
我们用Generator来实现:
//准备
function prepare(sucess){
setTimeout(function(){
console.log("prepare chicken");
sucess();
},500)
}
//炒鸡
function fired(sucess){
setTimeout(function(){
console.log("fired chicken");
sucess();
},500)
}
//炖鸡
function stewed(sucess){
setTimeout(function(){
console.log("stewed chicken");
sucess();
},500)
}
//上料
function sdd(sucess){
setTimeout(function(){
console.log("sdd chicken");
sucess();
},500)
}
//上菜
function serve(sucess){
setTimeout(function(){
console.log("serve chicken");
sucess();
},500)
}
//流程控制
function run(fn){
const gen = fn();
function next() {
const result = gen.next();
if (result.done) return;//结束
// result.value就是yield返回的值,是各个工序的函数
result.value(next);//next作为入参,即本工序成功后,执行下一工序
}
next();
};
//工序
function* task(){
yield prepare;
yield fired;
yield stewed;
yield sdd;
yield serve;
}
run(task);//开始执行
3 aysnc
3.1简介
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
async function helloAs(){
return 'helloAsync';
}
console.log(helloAs()); //Promise {<resolved>: "helloAsync"}
helloAs().then(v=>console.log(v)); //helloAsync
async函数对 Generator 函数的改进,体现在以下四点。
(1)内置执行器。
Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。
(2)更好的语义。
async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
(3)更广的适用性。
co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
(4)返回值是 Promise。
async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。
进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖
3.2 await 关键字
在Generator章节中我们熟悉了yield关键字,yield关键字只能使用在Generator函数中,同样,await关键字也不能单独使用,是需要使用在async方法中。 await字面意思是"等待",那它是在等什么呢?它是在等待后面表达式的执行结果。
function testAwait(){
return new Promise((resolve) => {
setTimeout(function(){
console.log("testAwait");
resolve();
}, 1000);
});
}
async function helloAsync(){
await testAwait();
console.log("helloAsync");
}
helloAsync();
我们来分析下这段代码
1、testAwait()方法中new一个Promise对象返回,promise对象中用setTimeout模拟一个异步过程,即1s后打印"testAwait"。
2、helloAsync()方法中,await testAwait(),表示将阻塞这里,等待testAwait这个异步方法执行并返回结果后,才继续下面的代码。
执行下,1s后打印了下面的日志
testAwait
helloAsync
await的作用,就是阻塞主函数的执行,直到后面的Promise函数返回结果,awit后面也可以是字符串、布尔值、数值以及普通函数
function testAwait(){
setTimeout(function(){
console.log("testAwait");
}, 1000);
}
async function helloAsync(){
await testAwait();
console.log("helloAsync");
}
helloAsync();
执行结果
helloAsync
testAwait
方法没有报错,说明await后面是支持非Promise函数的,但是执行的结果是不一样的,所以await针对所跟的表达式不同,有两种处理方式:
1、对于Promise对象,await会阻塞主函数的执行,等待 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果,然后继续执行主函数接下来的代码。
2、对于非Promise对象,await等待函数或者直接量的返回,而不是等待其执行结果
3.3 应用场景
由于await可以阻塞主函数,直到后面的promise对象执行完成,这个特性就可以解决按顺序控制异步操作。
在Generator章节的肚包鸡的制作过程的实例,我们用async/await来重写这个例子,并比较下两者实现的区别。
//准备
function prepare(){
return new Promise((resolve) => {
setTimeout(function(){
console.log("prepare chicken");
resolve();
},500)
});
}
//炒鸡
function fired(){
return new Promise((resolve) => {
setTimeout(function(){
console.log("fired chicken");
resolve();
},500)
});
}
//炖鸡
function stewed(){
return new Promise((resolve) => {
setTimeout(function(){
console.log("stewed chicken");
resolve();
},500)
});
}
//上料
function sdd(){
return new Promise((resolve) => {
setTimeout(function(){
console.log("sdd chicken");
resolve();
},500)
});
}
//上菜
function serve(){
return new Promise((resolve) => {
setTimeout(function(){
console.log("serve chicken");
resolve();
},500)
});
}
async function task(){
console.log("start task");
await prepare();
await fired();
await stewed();
await sdd();
await serve();
console.log("end task");
}
task();
4 promise、Generator、async分别实现读取文件
需求:分别读取文件1、2、3
回调函数会产生回调地狱,可以用promise、Generator、async将异步回调扁平化
- promise实现
const fs=require('fs');
//封装读取函数
function readFile(path){
return new Promise((resolve,reject)=>{
fs.readFile(path,'UTF-8',(err,dataStr)=>{
if(err) return reject(err);
resolve(dataStr);
})
})
}
readFile('files/1.txt').then((data)=>{
// 成功的回调
console.log(data);
return readFile('files/2.txt');
},(err)=>{
console.log(err);
}).then((data)=>{
console.log(data);
return readFile('files/3.txt');
},(err)=>{
console.log(err);
}).then((data)=>{
console.log(data);
},(err)=>{
console.log(err);
}).catch(function(err){
// catch的作用,如果前面有任何的promise执行失败,则立即终止promise的执行,捕获异常
console.log(err.message+'失败了')
})
缺点是.then的链式写法使语义不清
- Generator/yield
const fs=require('fs');
//封装读取模块
function readFile(path){
return new Promise((resolve,reject)=>{
fs.readFile(path,'UTF-8',(err,data)=>{
if(err) reject(err);
resolve(data);
})
})
}
//gen函数
function* gen(){
try{
var f1=yield readFile('./files/1.txt');
console.log(f1.toString());
var f2=yield readFile('./files/2.txt');
console.log(f2.toString());
var f3=yield readFile('./files/3.txt');
console.log(f3.toString());
}catch(e){
//handle err
console.log("出错啦");
}
}
//手动书写自动执行器
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);
Generator函数的缺点就是要手动书写run函数执行器,不然需要引入co模块使其自动执行
- async/await
const fs=require('fs');
//封装读取模块
function readFile(path){
return new Promise((resolve,reject)=>{
fs.readFile(path,'UTF-8',(err,data)=>{
if(err) reject(err);
resolve(data);
})
})
}
async function asRead(){
try{
var f1=await readFile('./files/1.txt');
console.log(f1.toString());
var f2=await readFile('./files/2.txt');
console.log(f2.toString());
var f3=await readFile('./files/3.txt');
console.log(f3.toString());
}catch(e){
console.log('error');
}
}
asRead();
async优化了Generator函数的表达。
参考文献
1、https://www.jianshu.com/p/3023a9372e5f/
2、https://www.jianshu.com/p/43de678e918a
3、https://www.jianshu.com/p/b4f0425b22a1
4、http://es6.ruanyifeng.com/#docs/promise
5、https://blog.csdn.net/ganyingxie123456/article/details/78152770
6、https://blog.csdn.net/astonishqft/article/details/82782422
7、https://blog.csdn.net/tcy83/article/details/80427195
8、https://blog.csdn.net/tcy83/article/details/80544048