ES6语法——异步(Promise、Generator、async)

http://es6.ruanyifeng.com/阮一峰ES6

异步,简单说就是一个任务不是连续完成的。比如,有一个任务是读取文件进行处理,向操作系统发出请求,要求读取文件;程序执行其他任务;等到操作系统返回文件,再处理文件。相应地,连续的执行就叫做同步,不能插入其它任务,操作系统从硬盘读取文件的这段时间,程序只能干等着。

异步编程的方法:回调函数、事件监听、发布/订阅、Promise对象、Generator函数。

回调函数callback:把任务的第二段单独写在一个函数中,等到重新执行这个任务时,直接调用这个函数。

// 读取文件进行处理
// 等到操作系统返回/etc/passwd这个文件后,回调函数才会执行。
fs.readFile('/etc/passwd','utf-8',function(err,data){
  if(err) throw err;
  console.log(data);
});
// 回调  写法复杂,难以分清执行顺序,影响维护
let ajax=function(callback){
  console.log("ajax");
  setTimeout(function(){
    callback&&callback.call()
  },1000);
}
ajax(function(){
  console.log('callback');
});

 

十三、Promise对象

Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大。

Promise是一个容器,保存着某个未来才会结束的事件(通常是一个异步操作)的结果。Promise是一个对象,从它可以获取异步操作的消息,提供统一的 API,各种异步操作都可以用同样的方法进行处理。

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,会一直保持这个结果,称为resolved(已定型)。如果改变已经发生了,再对Promise对象添加回调函数,也会立即得到这个结果。而对于事件(Event),如果错过了它再去监听,是得不到结果的。

Promise对象将异步操作以同步操作的流程表达,避免了层层嵌套的回调函数;提供统一的接口,使控制异步操作更加容易。也有一些缺点,一旦新建它就会立即执行,无法中途取消;如果不设置回调函数,Promise内部抛出的错误,不会反应到外部;当处于pending状态时,无法得知目前进展到哪一个阶段。

如果某些事件不断地反复发生,一般使用Stream模式比部署Promise更好。

1、new Promise()

Promise对象是一个构造函数,用来生成Promise实例。接受一个函数作为参数,该函数的参数分别是resolve和reject函数。

resolve函数将Promise对象的状态从“未完成”变为“成功”,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数将Promise对象的状态从“未完成”变为“失败”,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise新建后就会立即执行。调用resolve或reject以后,Promise的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject后面。最好在它们前面加上return语句。

const promise=new Promise(function(resolve,reject){
  // ... some code

  if(/* 异步操作成功 */){
    resolve(value);
  }else{
    reject(error);
  }
});

2、Promise.prototype.then()、catch()和finally()

Promise实例生成后,用then方法分别指定resolve和rejected状态的回调函数(异步操作无法使用return把结果返回给调用者),第二个函数是可选的。

返回新的Promise实例,可以采用链式写法,指定一组按照次序调用的回调函数。前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),后一个回调函数,会等待该Promise对象的状态发生变化,才会被调用。

promise.then(function(value){
  // success
},function(error){
  // failure
});
/** 执行步骤:(假设有一个.then)
1. 定义函数ajax;
2. 执行ajax();
3. 打印"ajax",new Promise()并返回;
   function(resolve,reject)开始异步执行;
4. .then指定了function(resolve,reject)的回调;
5. function(resolve,reject)执行完。
*/
let ajax=function(){
  console.log("ajax");
  return new Promise(function(resolve,reject){
    setTimeout(resolve,1000);
  })
}

ajax()
.then(function(){
  console.log("timeout1");
  return new Promise(function(resolve,reject){
    setTimeout(resolve,2000);
  })
})
.then(function(){
  console.log("timeout2")
})

catch方法是.then(null,rehection)或.then(undefined,rejection)的别名,用于指定发生错误时(rejected状态)的回调函数。一般不要在then方法使用第二个参数),总是使用catch方法。Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止,即错误总是会被下一个catch语句捕获。

Promise对象抛出的错误不会传递到外层代码,不会终止程序。

const promise=new Promise(function(resolve,reject){
  throw new Error('test');
});
promise.catch(function(error){
  console.log(error);
})
let ajax=function(){
  return new Promise(function(resolve,reject){
    throw new Error("promise1 error");
    return resolve();
  })
}

/* 错误一直向后传递,直到catch捕获,跳过中间的.then */
ajax()
.then(function(){
  console.log("promise1");
  return new Promise(function(resolve,reject){
    return resolve();
  })
})
.then(function(){
  console.log("promise2");
})
.catch(function(){
  console.log("reject");
})

console.log("outside");
// outside
// reject

/* 每一步.then通过reject处理错误,不影响之后的.then */
ajax()
.then(function(){
  console.log("promise1");
  return new Promise(function(resolve,reject){
    return resolve();
  })
},function(error){
  console.log("reject"+error);
})
.then(function(){
  console.log("promise2");
})

console.log("outside");
// outside
// rejectError: promise1 error
// promise2

finally方法指定不管Promise对象最后状态如何,都会执行的操作。

server.listen(port)
  .then(function(){
    // ...
  })
  .finally(server.stop);

3、Promise.all()和race()

Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。

参数必须具有Iterator接口,且返回的每个成员都是Promise实例,否则调用Promise.resolve方法将成员转为Promise实例。

只有所有成员的状态是fulfilled,p的状态才是fulfilled,此时将所有成员的返回值组成一个数组传给p的回调函数。如果p的状态是rejected,将第一个被reject的实例的返回值传给p的回调函数。

const p=Promise.all([p1,p2,p3]);
/* 所有图片加载完再添加到页面 */
function loadImg(src){
  return new Promise((resolve,reject)=>{
    let img=document.createElement('img');
    img.src=src;
    img.οnlοad=function(){
      resolve(img);
    }
    img.οnerrοr=function(err){
      reject(err);
    }
  })
}
function showImgs(imgs){
  imgs.forEach(function(img){
    document.body.appendChild(img);
  })
}
Promise.all([
  loadImg("https://img-blog.csdn.net/20180830223234851?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lhb2NvbmcxOTkz/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70"),
  loadImg("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1546709317586&di=b8a1d3ca2d74a9a0e143a08487a753b5&imgtype=0&src=http%3A%2F%2Fdesk-fd.zol-img.com.cn%2Ft_s960x600c5%2Fg5%2FM00%2F02%2F03%2FChMkJlbKxpKIBV3oABpO1k7kXwEAALHnQB8IREAGk7u679.jpg"),
  loadImg("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1546709346183&di=714f1f97a96934a9a3ee1d42a5fbd775&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201405%2F22%2F20140522162020_jJPVm.jpeg")
]).then(showImgs)

Promise.race方法与Promise.all方法的不同之处在于,只要成员中有一个实例率先改变状态,p的状态就跟着改变。率先改变的Promise实例的返回值,就传递给p的回调函数。

/* 有一张图片加载完就添加到页面 */
function showImg(img){
    document.body.appendChild(img);
}
Promise.race([
  loadImg("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1546709317586&di=b8a1d3ca2d74a9a0e143a08487a753b5&imgtype=0&src=http%3A%2F%2Fdesk-fd.zol-img.com.cn%2Ft_s960x600c5%2Fg5%2FM00%2F02%2F03%2FChMkJlbKxpKIBV3oABpO1k7kXwEAALHnQB8IREAGk7u679.jpg"),
  loadImg("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1546709346183&di=714f1f97a96934a9a3ee1d42a5fbd775&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201405%2F22%2F20140522162020_jJPVm.jpeg"),
  loadImg("https://img-blog.csdn.net/20180830223234851?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lhb2NvbmcxOTkz/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70")
]).then(showImg)

4、Promise.resolve()和reject()

Promise.resolve方法将现有对象转为Promise对象。

Promise.reject(reason)方法返回一个新的Promise实例,该实例的状态为rejected。参数会原封不动地作为reject的理由,变成后续方法的参数。

十四、Generator函数

1、简介

语法上,Generator函数是一个状态机,封装了多个内部状态;返回一个遍历器对象,可以依次遍历Generator函数内部的每一个状态。形式上,Generator函数是一个普通函数,但function关键字与函数名之间有一个星号,函数体内部使用yield表达式定义不同的内部状态。

function* helloWorldGenerator(){
  yield 'hello';
  yield 'world';
  return 'ending';
}
let hw=helloWorldGenerator();
console.log(hw.next()); // {value: "hello", done: false}
console.log(hw.next()); // {value: "world", done: false}
console.log(hw.next()); // {value: "ending", done: true}
console.log(hw.next()); // {value: undefined, done: true}

上述函数有三个状态hello,world和return语句(结束执行)。调用Generator函数后,该函数并不执行,返回一个指向内部状态的指针对象(遍历器对象)。每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。将yield或return后面表达式的值作为返回的对象的value值。

Generator函数是分段执行的,yield表达式是暂停执行的标记,next方法可以恢复执行。可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数。

2、状态机

let clock=function* (){
  while(true){
    console.log("Tick!");
    yield;
    console.log("Tock!");
    yield;
  }
}
let status=clock();
status.next(); // Tick!
status.next(); // Tock!
status.next(); // Tick!
status.next(); // Tock!

十五、async函数

async函数就是将Generator函数的星号(*)替换成async,将yield替换成await。对Generator函数的改进,体现在以下几点。

(1)内置执行器:才能真正执行,而async函数自带执行器,执行与普通函数相同,不需要调用next方法或用co模块。

(2)更好的语义:对比星号和yield,async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

(3)更广的适用性:async函数的await命令后面,可以是Promise对象和原始类型的值(自动转成立即resolved的Promise对象)。

(4)返回值是Promise。

async函数可以看作多个异步操作,包装成的一个Promise对象,而await命令是内部then命令的语法糖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章