- Promise对象
- Promise的含义
-
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。
Promise对象有以下两个特点。
-
对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
-
一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
如果某些事件不断地反复发生,一般来说,使用stream模式是比部署Promise更好的选择。
-
-
-
基本用法
let promise = new Promise(function(resolve, reject) { console.log('Promise'); resolve(); }); promise.then(function() { console.log('Resolved.'); }); console.log('Hi!'); // Promise // Hi! // Resolved
-
Promise.prototype.then()
getJSON("/post/1.json").then(function(post) { return getJSON(post.commentURL); }).then(function funcA(comments) { console.log("Resolved: ", comments); }, function funcB(err){ console.log("Rejected: ", err); });
-
Promise.prototype.catch()
p.then((val) => console.log("fulfilled:", val)) .catch((err) => console.log("rejected:", err)); // 等同于 p.then((val) => console.log("fulfilled:", val)) .then(null, (err) => console.log("rejected:", err));
-
Promise.all()
-
Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。
var p = Promise.all([p1, p2, p3]);
上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是Promise对象的实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。)
p的状态由p1、p2、p3决定,分成两种情况。
-
只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
-
只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
// 生成一个Promise对象的数组 var promises = [2, 3, 5, 7, 11, 13].map(function (id) { return getJSON("/post/" + id + ".json"); }); Promise.all(promises).then(function (posts) { // ... }).catch(function(reason){ // ... });
-
-
-
Promise.race()
-
Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。
var p = Promise.race([p1, p2, p3]);
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
Promise.race方法的参数与Promise.all方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
下面是一个例子,如果指定时间内没有获得结果,就将Promise的状态变为reject,否则变为resolve。
var p = Promise.race([ fetch('/resource-that-may-take-a-while'), new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('request timeout')), 5000) }) ]) p.then(response => console.log(response)) p.catch(error => console.log(error))
上面代码中,如果5秒之内
fetch
方法无法返回结果,变量p
的状态就会变为rejected
,从而触发catch
方法指定的回调函数。
-
-
Promise.resolve()
-
有时需要将现有对象转为Promise对象,
Promise.resolve
方法就起到这个作用。Promise.resolve('foo') // 等价于 new Promise(resolve => resolve('foo'))
-
-
Promise.reject()
-
Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected。它的参数用法与Promise.resolve方法完全一致。
var p = Promise.reject('出错了'); // 等同于 var p = new Promise((resolve, reject) => reject('出错了')) p.then(null, function (s){ console.log(s) }); // 出错了
-
-
两个有用的附加方法
-
done()
-
Promise对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误不会冒泡到全局)。因此,我们可以提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。
asyncFunc() .then(f1) .catch(r1) .then(f2) .done();
-
-
finally()
-
finally方法用于指定不管Promise对象最后状态如何,都会执行的操作。它与done方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。
下面是一个例子,服务器使用Promise处理请求,然后使用finally方法关掉服务器。
server.listen(0) .then(function () { // run test }) .finally(server.stop);
-
-
- Promise的含义
- Async函数
- ES7提供了async函数,使得异步操作变得更加方便。
- 语法
- async函数返回一个Promise对象。
- async函数内部return语句返回的值,会成为then方法回调函数的参数。
async function f() { return 'hello world'; } f().then(v => console.log(v)) // "hello world"
- async函数内部抛出错误,会导致返回的Promise对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
async function f() { throw new Error('出错了'); } f().then( v => console.log(v), e => console.log(e) ) // Error: 出错了
- async函数内部return语句返回的值,会成为then方法回调函数的参数。
- async函数返回的Promise对象,必须等到内部所有await命令的Promise对象执行完,才会发生状态改变。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
- 正常情况下,await命令后面是一个Promise对象。如果不是,会被转成一个立即resolve的Promise对象。
async function f() { return await 123; } f().then(v => console.log(v)) // 123
- 如果await后面的异步操作出错,那么等同于async函数返回的Promise对象被reject。
async function f() { try { await new Promise(function (resolve, reject) { throw new Error('出错了'); }); } catch(e) { } return await('hello world'); }
- async函数返回一个Promise对象。
- 注意点
- await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。
async function myFunction() { try { await somethingThatReturnsAPromise(); } catch (err) { console.log(err); } } // 另一种写法 async function myFunction() { await somethingThatReturnsAPromise() .catch(function (err) { console.log(err); }; }
- 多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
let foo = await getFoo(); let bar = await getBar();
上面代码中,getFoo和getBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。
// 写法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 写法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise;
上面两种写法,getFoo和getBar都是同时触发,这样就会缩短程序的执行时间。
- await命令只能用在async函数之中,如果用在普通函数,就会报错。
async function dbFuc(db) { let docs = [{}, {}, {}]; // 报错 docs.forEach(function (doc) { await db.post(doc); }); }
上面代码会报错,因为await用在普通函数之中了。但是,如果将forEach方法的参数改成async函数,也有问题。
async function dbFuc(db) { let docs = [{}, {}, {}]; // 可能得到错误结果 docs.forEach(async function (doc) { await db.post(doc); }); }
上面代码可能不会正常工作,原因是这时三个db.post操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用for循环。
async function dbFuc(db) { let docs = [{}, {}, {}]; for (let doc of docs) { await db.post(doc); } }
- await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。
- Class基本语法
- 概述
- 示例
//定义类 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } }
此外,类的数据类型就是函数,类本身就指向构造函数。
class Point { // ... } typeof Point // "function" Point === Point.prototype.constructor // true
构造函数的
prototype
属性,在ES6的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype
属性上面。class Point { constructor(){ // ... } toString(){ // ... } toValue(){ // ... } } // 等同于 Point.prototype = { toString(){}, toValue(){} };
在类的实例上面调用方法,其实就是调用原型上的方法。
class B {} let b = new B(); b.constructor === B.prototype.constructor // true
另外,在ES6中,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。
class Point { constructor(x, y) { // ... } toString() { // ... } } Object.keys(Point.prototype) // [] Object.getOwnPropertyNames(Point.prototype) // ["constructor","toString"]
- 示例
- constructor方法
- constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
constructor() {}
constructor
方法默认返回实例对象(即this
),完全可以指定返回另外一个对象。下面代码中,constructor
函数返回一个全新的对象,结果导致实例对象不是Foo
类的实例。class Foo { constructor() { return Object.create(null); } } new Foo() instanceof Foo // false
- constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
- 类的实例对象
- 不存在变量提升,这点与ES5不同
- Class表达式
- 与函数一样,类也可以使用表达式的形式定义。
const MyClass = class Me { getClassName() { return Me.name; } };
上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是
MyClass
而不是Me
,Me
只在Class的内部代码可用,指代当前类。const MyClass = class Me { getClassName() { return Me.name; } } let mc = new MyClass() console.log(mc.getClassName()) //Me
- 与函数一样,类也可以使用表达式的形式定义。
- 私有方法
-
私有方法是常见需求,但ES6不提供,只能通过变通方法模拟实现。一种做法是在命名上加以区别。
class Widget { // 公有方法 foo (baz) { this._bar(baz); } // 私有方法 _bar(baz) { return this.snaf = baz; } // ... }
上面代码中,_bar方法前面的下划线,表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部,还是可以调用到这个方法。
另一种方法就是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的。
class Widget { foo (baz) { bar.call(this, baz); } // ... } function bar(baz) { return this.snaf = baz; }
上面代码中,foo是公有方法,内部调用了bar.call(this, baz)。这使得bar实际上成为了当前模块的私有方法。
还有一种方法是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值。
const bar = Symbol('bar'); const snaf = Symbol('snaf'); export default class myClass{ // 公有方法 foo(baz) { this[bar](baz); } // 私有方法 [bar](baz) { return this[snaf] = baz; } // ... };
上面代码中,bar和snaf都是Symbol值,导致第三方无法获取到它们,因此达到了私有方法和私有属性的效果。
-
- this的指向
- 类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。下面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境,因为找不到print方法而导致报错。
class Logger { printName(name = 'there') { this.print(`Hello ${name}`); } print(text) { console.log(text); } } const logger = new Logger(); const { printName } = logger; printName(); // TypeError: Cannot read property 'print' of undefined
一种有效的解决方法是使用箭头函数。
class Logger{ printName = (name = 'wang')=>{ return this.print(`hello,${name}`) } print(text){ console.log(text) } } let log = new Logger() log.printName() //hello,wang
- 类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。下面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境,因为找不到print方法而导致报错。
- name属性
- 由于本质上,ES6的类只是ES5的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。name属性总是返回紧跟在class关键字后面的类名。
class Point {} Point.name // "Point"
- 由于本质上,ES6的类只是ES5的构造函数的一层包装,所以函数的许多特性都被Class继承,包括name属性。name属性总是返回紧跟在class关键字后面的类名。
- Class的继承
- Class之间可以通过extends关键字实现继承
class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 调用父类的constructor(x, y) this.color = color; } toString() { return this.color + ' ' + super.toString(); // 调用父类的toString() } }
- 类的prototype属性和__proto__属性
- 子类的__proto__属性,表示构造函数的继承,总是指向父类。
- 子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
class A { } class B extends A { } B.__proto__ === A // true B.prototype.__proto__ === A.prototype // true
- Class之间可以通过extends关键字实现继承
- Extends 的继承目标
- extends关键字后面可以跟多种类型的值。
- 第一种特殊情况,子类继承Object类。
class A extends Object { } A.__proto__ === Object // true A.prototype.__proto__ === Object.prototype // true
- 第二种特殊情况,不存在任何继承。
class A { } A.__proto__ === Function.prototype // true A.prototype.__proto__ === Object.prototype // true
这种情况下,A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承Funciton.prototype。但是,A调用后返回一个空对象(即Object实例),所以A.prototype.__proto__指向构造函数(Object)的prototype属性。
- 第三种特殊情况,子类继承null。
class A extends null { } A.__proto__ === Function.prototype // true A.prototype.__proto__ === undefined // true
- 第一种特殊情况,子类继承Object类。
- extends关键字后面可以跟多种类型的值。
- Object.getPrototypeOf()
- Object.getPrototypeOf方法可以用来从子类上获取父类。因此,可以使用这个方法判断,一个类是否继承了另一个类。
Object.getPrototypeOf(ColorPoint) === Point // true
- Object.getPrototypeOf方法可以用来从子类上获取父类。因此,可以使用这个方法判断,一个类是否继承了另一个类。
- super 关键字
- 实例的__proto__属性
- 子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。
var p1 = new Point(2, 3); var p2 = new ColorPoint(2, 3, 'red'); p2.__proto__ === p1.__proto__ // false p2.__proto__.__proto__ === p1.__proto__ // true
上面代码中,ColorPoint继承了Point,导致前者原型的原型是后者的原型。
p2.__proto__.__proto__.printName = function () { console.log('Ha'); }; p1.printName() // "Ha"
因此,通过子类实例的__proto__.__proto__属性,可以修改父类实例的行为。
- 子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。也就是说,子类的原型的原型,是父类的原型。
-
Class的取值函数(getter)和存值函数(setter)
-
在Class内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
class MyClass { constructor() { // ... } get prop() { return 'getter'; } set prop(value) { console.log('setter: '+value); } } let inst = new MyClass(); inst.prop = 123; // setter: 123 inst.prop // 'getter'
-
-
Class的静态方法
-
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Foo { static classMethod() { return 'hello'; } } Foo.classMethod() // 'hello' var foo = new Foo(); foo.classMethod() // TypeError: foo.classMethod is not a function
上面代码中,
Foo
类的classMethod
方法前有static
关键字,表明该方法是一个静态方法,可以直接在Foo
类上调用(Foo.classMethod()
),而不是在Foo
类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。父类的静态方法,可以被子类继承。
class Foo { static classMethod() { return 'hello'; } } class Bar extends Foo { } Bar.classMethod(); // 'hello'
上面代码中,父类
Foo
有一个静态方法,子类Bar
可以调用这个方法。静态方法也是可以从super
对象上调用的。class Foo { static classMethod() { return 'hello'; } } class Bar extends Foo { static classMethod() { return super.classMethod() + ', too'; } } console.log(Bar.classMethod()) //hello, too
-
- Class的静态属性和实例属性
- 静态属性指的是Class本身的属性,即Class.propname,而不是定义在实例对象(this)上的属性。
class Foo { } Foo.prop = 1; Foo.prop // 1
-
ES7有一个静态属性的提案,目前Babel转码器支持。这个提案对实例属性和静态属性,都规定了新的写法。
-
类的实例属性。类的实例属性可以用等式,写入类的定义之中。
class MyClass { myProp = 42; constructor() { console.log(this.myProp); // 42 } }
-
类的静态属性。类的静态属性只要在上面的实例属性写法前面,加上
static
关键字就可以了。class MyClass { static myStaticProp = 42; constructor() { console.log(MyClass.myStaticProp); // 42 } } let mc = new MyClass()
-
- 静态属性指的是Class本身的属性,即Class.propname,而不是定义在实例对象(this)上的属性。
- 概述