简介
ES6中除了上篇文章讲过的语法新个性和一些新的API之外,还有两个十分重要的新个性就是Promise和Generator,明天咱们将会具体解说一下这两个新个性。
Promise
什么是Promise
Promise 是异步编程的一种解决方案,比传统的解决方案“回调函数和事件”更正当和更弱小。
所谓Promise,简略说就是一个容器,外面保留着某个将来才会完结的事件(通常是一个异步操作)的后果。
从语法上说,Promise 是一个对象,从它能够获取异步操作的音讯。
Promise的特点
Promise有两个特点:
- 对象的状态不受外界影响。
Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已实现,又称 Fulfilled)和Rejected(已失败)。
只有异步操作的后果,能够决定以后是哪一种状态,任何其余操作都无奈扭转这个状态。
- 一旦状态扭转,就不会再变,任何时候都能够失去这个后果。
Promise对象的状态扭转,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。
这与事件(Event)齐全不同,事件的特点是,如果你错过了它,再去监听,是得不到后果的。
Promise的长处
Promise将异步操作以同步操作的流程表达出来,防止了层层嵌套的回调函数。
Promise对象提供对立的接口,使得管制异步操作更加容易。
Promise的毛病
- 无奈勾销Promise,一旦新建它就会立刻执行,无奈中途勾销。
- 如果不设置回调函数,Promise外部抛出的谬误,不会反馈到内部。
- 当处于Pending状态时,无奈得悉目前停顿到哪一个阶段(刚刚开始还是行将实现)。
Promise的用法
Promise对象是一个构造函数,用来生成Promise实例:
var promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作胜利 */){ resolve(value); } else { reject(error); } });
promise能够接then操作,then操作能够接两个function参数,第一个function的参数就是构建Promise的时候resolve的value,第二个function的参数就是构建Promise的reject的error。
promise.then(function(value) { // success }, function(error) { // failure });
咱们看一个具体的例子:
function timeout(ms){ return new Promise(((resolve, reject) => { setTimeout(resolve,ms,'done'); }))}timeout(100).then(value => console.log(value));
Promise中调用了一个setTimeout办法,并会定时触发resolve办法,并传入参数done。
最初程序输入done。
Promise的执行程序
Promise一经创立就会立马执行。然而Promise.then中的办法,则会等到一个调用周期过后再次调用,咱们看上面的例子:
let promise = new Promise(((resolve, reject) => { console.log('Step1'); resolve();}));promise.then(() => { console.log('Step3');});console.log('Step2');输入:Step1Step2Step3
Promise.prototype.then()
then办法返回的是一个新的Promise实例(留神,不是原来那个Promise实例)。因而能够采纳链式写法,即then办法前面再调用另一个then办法.
getJSON("/users.json").then(function(json){ return json.name;}).then(function(name){ console.log(name);});
下面的代码应用then办法,顺次指定了两个回调函数。第一个回调函数实现当前,会将返回后果作为参数,传入第二个回调函数
Promise.prototype.catch()
Promise.prototype.catch办法是.then(null, rejection)的别名,用于指定产生谬误时的回调函数。
getJSON("/users.json").then(function(json){ return json.name;}).catch(function(error){ console.log(error);});
Promise 对象的谬误具备“冒泡”性质,会始终向后传递,直到被捕捉为止。也就是说,谬误总是会被下一个catch语句捕捉
getJSON("/users.json").then(function(json){ return json.name;}).then(function(name){ console.log(name);}).catch(function(error){ //解决后面所有产生的谬误 console.log(error);});
Promise.all()
Promise.all办法用于将多个Promise实例,包装成一个新的Promise实例
var p = Promise.all([p1,p2,p3]);
- 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
- 只有p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
Promise.race()
Promise.race办法同样是将多个Promise实例,包装成一个新的Promise实例
var p = Promise.race([p1,p2,p3]);
只有p1、p2、p3之中有一个实例率先扭转状态,p的状态就跟着扭转。那个率先扭转的 Promise 实例的返回值,就传递给p的回调函数.
Promise.resolve()
Promise.resolve()将现有对象转为Promise对象.
Promise.resolve('js');//等价于new Promise(resolve => resolve('js'));
那么什么样的对象可能转化成为Promise对象呢?
- 参数是一个Promise实例
- 参数是一个thenable对象
- 参数不是具备then办法的对象,或基本就不是对象
- 不带有任何参数
Promise.reject()
Promise.reject(reason)办法也会返回一个新的 Promise 实例,该实例的状态为rejected
var p = Promise.reject('error');//等价于var p = new Promise((resolve,reject) => reject('error'));
Promise.reject()办法的参数,会一成不变地作为reject的理由,变成后续办法的参数。这一点与Promise.resolve办法不统一
done()
Promise对象的回调链,不论以then办法或catch办法结尾,要是最初一个办法抛出谬误,都有可能无奈捕捉到(因为Promise外部的谬误不会冒泡到全局)。因而,咱们能够提供一个done办法,总是处于回调链的尾端,保障抛出任何可能呈现的谬误
asyncFunc().then(f1).catch(f2).then(f3).done();
finally()
finally办法用于指定不论Promise对象最初状态如何,都会执行的操作。它与done办法的最大区别,它承受一个一般的回调函数作为参数,该函数不管怎样都必须执行.
server.listen(1000).then(function(){ //do something}.finally(server.stop);
Generator
什么是Generator
Generator 函数是 ES6 提供的一种异步编程解决方案
从语法上,首先能够把它了解成,Generator函数是一个状态机,封装了多个外部状态
执行 Generator 函数会返回一个遍历器对象.
模式上,Generator 函数是一个一般函数,然而有两个特色。一是,function关键字与函数名之间有一个星号;二是,函数体外部应用yield语句,定义不同的外部状态。
举个例子:
function * helloWorldGenerator(){ yield 'hello'; yield 'world'; return 'ending';}var gen = helloWorldGenerator();
输入后果:
console.log(gen.next());console.log(gen.next());console.log(gen.next());{ value: 'hello', done: false }{ value: 'world', done: false }{ value: 'ending', done: true }
yield
遍历器对象的next办法的运行逻辑如下:
(1)遇到yield语句,就暂停执行前面的操作,并将紧跟在yield前面的那个表达式的值,作为返回的对象的value属性值。
(2)下一次调用next办法时,再持续往下执行,直到遇到下一个yield语句。
(3)如果没有再遇到新的yield语句,就始终运行到函数完结,直到return语句为止,并将return语句前面的表达式的值,作为返回的对象的value属性值。
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。
留神,yield句自身没有返回值,或者说总是返回undefined。
next办法能够带一个参数,该参数就会被当作上一个yield语句的返回值。
function * f() { for( let i =0; true; i++){ let reset = yield i; if(reset){ i = -1; } }}let g = f();console.log(g.next());console.log(g.next());console.log(g.next(true));
输入后果:
{ value: 0, done: false }{ value: 1, done: false }{ value: 0, done: false }
能够看到最初的一步,咱们应用next传入的true代替了i的值,最初导致i= -1 + 1 = 0.
咱们再看一个例子:
function * f2(x){ var y = 2 * ( yield ( x + 1)); var z = yield (y / 3); return (x + y + z);}var r1= f2(5);console.log(r1.next());console.log(r1.next());console.log(r1.next());var r2= f2(5);console.log(r2.next());console.log(r2.next(12));console.log(r2.next(13));
输入后果:
{ value: 6, done: false }{ value: NaN, done: false }{ value: NaN, done: true }{ value: 6, done: false }{ value: 8, done: false }{ value: 42, done: true }
如果next不传值的话,yield自身是没有返回值的,所以咱们会失去NaN。
然而如果next传入特定的值,则该值会替换该yield,成为真正的返回值。
yield *
如果在 Generator 函数外部,调用另一个 Generator 函数,默认状况下是没有成果的
function * a1(){ yield 'a'; yield 'b';}function * b1(){ yield 'x'; a1(); yield 'y';}for(let v of b1()){ console.log(v);}
输入后果:
xy
能够看到,在b1中调用a1是没有成果的。
将下面的例子批改一下:
function * a1(){ yield 'a'; yield 'b';}function * b1(){ yield 'x'; yield * a1(); yield 'y';}for(let v of b1()){ console.log(v);}
输入后果:
xaby
异步操作的同步化表白
Generator函数的暂停执行的成果,意味着能够把异步操作写在yield语句外面,等到调用next办法时再往后执行。这实际上等同于不须要写回调函数了,因为异步操作的后续操作能够放在yield语句上面,反正要等到调用next办法时再执行。所以,Generator函数的一个重要实际意义就是用来解决异步操作,改写回调函数。
咱们看一个怎么通过Generator来获取一个Ajax的后果。
function * ajaxCall(){ let result = yield request("http://www.flydean.com"); let resp = JSON.parse(result); console.log(resp.value);}function request(url){ makeAjaxCall(url, function(response){ it.next(response); });}var it = ajaxCall();it.next();
咱们应用一个yield来获取异步执行的后果。然而咱们如何将这个yield传给result变量呢?要记住yield自身是没有返回值的。
咱们须要调用generator的next办法,将异步执行的后果传进去。这就是咱们在request办法中做的事件。
Generator 的异步利用
什么是异步利用呢?
所谓"异步",简略说就是一个工作不是间断实现的,能够了解成该工作被人为分成两段,先执行第一段,而后转而执行其余工作,等做好了筹备,再回过头执行第二段。
比方,有一个工作是读取文件进行解决,工作的第一段是向操作系统发出请求,要求读取文件。而后,程序执行其余工作,等到操作系统返回文件,再接着执行工作的第二段(解决文件)。这种不间断的执行,就叫做异步。
相应地,间断的执行就叫做同步。因为是间断执行,不能插入其余工作,所以操作系统从硬盘读取文件的这段时间,程序只能干等着。
ES6诞生以前,异步编程的办法,大略有上面四种。
回调函数
事件监听
公布/订阅
Promise 对象
回调函数
fs.readFile(fileA, 'utf-8', function(error,data){ fs.readFile(fileB, 'utf-8', function(error,data){}})
如果顺次读取两个以上的文件,就会呈现多重嵌套。代码不是纵向倒退,而是横向发展,很快就会乱成一团,无奈治理。因为多个异步操作造成了强耦合,只有有一个操作须要批改,它的下层回调函数和上层回调函数,可能都要跟着批改。这种状况就称为"回调函数天堂"(callback hell)。
Promise
Promise 对象就是为了解决这个问题而提出的。它不是新的语法性能,而是一种新的写法,容许将回调函数的嵌套,改成链式调用。
let readFile = require('fs-readfile-promise');readFile(fileA).then(function(){ return readFile(fileB);}).then(function(data){ console.log(data);})
Thunk函数和异步函数主动执行
在讲Thunk函数之前,咱们讲一下函数的调用有两种形式,一种是传值调用,一种是传名调用。
"传值调用"(call by value),即在进入函数体之前,就计算x + 5的值(等于6),再将这个值传入函数f。C语言就采纳这种策略。
“传名调用”(call by name),即间接将表达式x + 5传入函数体,只在用到它的时候求值。
编译器的“传名调用”实现,往往是将参数放到一个长期函数之中,再将这个长期函数传入函数体。这个长期函数就叫做 Thunk 函数。
举个例子:
function f(m){ return m * 2;}f(x + 5);
下面的代码等于:
var thunk = function () { return x + 5;}function f(thunk){ return thunk() * 2;}
在 JavaScript 语言中,Thunk函数替换的不是表达式,而是多参数函数,将其替换成一个只承受回调函数作为参数的单参数函数。
怎么解释呢?
比方nodejs中的:
fs.readFile(filename,[encoding],[callback(err,data)])
readFile接管3个参数,其中encoding是可选的。咱们就以两个参数为例。
一般来说,咱们这样调用:
fs.readFile(fileA,callback);
那么有没有方法将其改写成为单个参数的function的级联调用呢?
var Thunk = function (fn){ return function (...args){ return functon (callback){ return fn.call(this,...args, callback); } }}var readFileThunk = Thunk(fs.readFile);readFileThunk(fileA)(callback);
能够看到下面的Thunk将两个参数的函数改写成为了单个参数函数的级联形式。或者说Thunk是接管一个callback并执行办法的函数。
这样改写有什么用呢?Thunk函数当初能够用于 Generator 函数的主动流程治理。
之前在讲Generator的时候,如果Generator中有多个yield的异步办法,那么咱们须要在next办法中传入这些异步办法的执行后果。
手动传入异步执行后果当然是能够的。然而有没有主动执行的方法呢?
let fs = require('fs');let thunkify = require('thunkify');let readFileThunk = thunkify(fs.readFile);let gen = function * (){ let r1 = yield readFileThunk('/tmp/file1'); console.log(r1.toString()); let r2 = yield readFileThunk('/tmp/file2'); console.log(r2.toString());}let g = gen();function run(fn){ let gen = fn(); function next (err, data){ let result = gen.next(data); if(result.done) return; result.value(next); } next();}run(g);
gen.next返回的是一个对象,对象的value就是Thunk函数,咱们向Thunk函数再次传入next callback,从而登程下一次的yield操作。
有了这个执行器,执行Generator函数不便多了。不论外部有多少个异步操作,间接把 Generator 函数传入run函数即可。当然,前提是每一个异步操作,都要是Thunk函数,也就是说,跟在yield命令前面的必须是Thunk函数。
总结
Promise和Generator是ES6中引入的十分中要的语法,前面的koa框架就是Generator的一种具体的实现。咱们会在前面的文章中具体解说koa的应用,敬请期待。
本文作者:flydean程序那些事本文链接:http://www.flydean.com/es6-promise-generator/
本文起源:flydean的博客
欢送关注我的公众号:「程序那些事」最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!