Promise的用法

终于意识到要写博客的第一天
我终于意识到写博客的重要性,哎,现在开始也不太晚吧,总比不开始强
今天主要弄明白以下几个问题吧

  • 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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Promise是一种异步编程的解决方案,它可以避免回调地狱,使得异步操作更加清晰和易于维护。Promise有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。当Promise处于pending状态时,可以转换为fulfilled或rejected状态,一旦转换为其中一种状态,就不可再转换为其他状态。 Promise的基本用法如下: ```javascript const promise = new Promise((resolve, reject) => { // 异步操作 if (/* 异步操作成功 */) { resolve(value); // 将Promise状态设置为fulfilled,并将异步操作的结果传递给回调函数 } else { reject(error); // 将Promise状态设置为rejected,并将错误信息传递给回调函数 } }); promise.then((value) => { // 异步操作成功时的回调函数 }, (error) => { // 异步操作失败时的回调函数 }); ``` 另外,Promise还提供了一些静态方法,如Promise.resolve和Promise.reject,可以方便地创建已经处于fulfilled或rejected状态的Promise对象,以及Promise.all和Promise.race等方法,可以处理多个Promise对象的状态。 下面是一个使用Promise的例子,通过异步加载图片并在加载完成后显示图片: ```javascript function loadImageAsync(url) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = function() { resolve(img); }; img.onerror = function() { reject(new Error('Could not load image at ' + url)); }; img.src = url; }); } loadImageAsync('https://example.com/image.jpg') .then((img) => { document.body.appendChild(img); }) .catch((error) => { console.error(error); }); ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值