10. Promise 对象
Promise 是异步编程的一种解决方案。
Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为
rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)
const someAsyncThing = function(flag) { return new Promise(function(resolve, reject) { if(flag){ resolve('ok'); }else{ reject('error') } }); }; someAsyncThing(true).then((data)=> { console.log('data:',data); // 输出 'ok' }).catch((error)=>{ console.log('error:', error); // 不执行 }) someAsyncThing(false).then((data)=> { console.log('data:',data); // 不执行 }).catch((error)=>{ console.log('error:', error); // 输出 'error' })
上面代码中,someAsyncThing 函数成功返回 ‘OK’, 失败返回 ‘error’, 只有失败时才会被 catch 捕捉到。
最简单实现:
// 发起异步请求 fetch('/api/todos') .then(res => res.json()) .then(data => ({ data })) .catch(err => ({ err }));
来看一道有意思的面试题:
setTimeout(function() { console.log(1) }, 0); new Promise(function executor(resolve) { console.log(2); for( var i=0 ; i<10000 ; i++ ) { i == 9999 && resolve(); } console.log(3); }).then(function() { console.log(4); }); console.log(5);
这道题应该考察 JavaScript 的运行机制的。
首先先碰到一个 setTimeout,于是会先设置一个定时,在定时结束后将传递这个函数放到任务队列里面,因此开始肯定不会输出 1 。
然后是一个 Promise,里面的函数是直接执行的,因此应该直接输出 2 3 。
然后,Promise 的 then 应当会放到当前 tick 的最后,但是还是在当前 tick 中。
因此,应当先输出 5,然后再输出 4 。
最后在到下一个 tick,就是 1 。
答案: “2 3 5 4 1”
11. async 函数
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
async 函数的使用方式,直接在普通函数前面加上 async,表示这是一个异步函数,在要异步执行的语句前面加上 await,表示后面的表达式需要等待。async 是 Generator 的语法糖
- async 函数内部 return 语句返回的值,会成为 then 方法回调函数的参数。
async function f() { return 'hello world'; } f().then(v => console.log(v)) // "hello world"
上面代码中,函数 f 内部 return 命令返回的值,会被 then 方法回调函数接收到。
- async 函数内部抛出错误,会导致返回的 Promise 对象变为 reject 状态。抛出的错误对象会被 catch 方法回调函数接收到。
async function f() { throw new Error('出错了'); } f().then( result => console.log(result), error => console.log(error) ) // Error: 出错了
- async 函数返回的 Promise 对象,必须等到内部所有 await 命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到 return 语句或者抛出错误。也就是说,只有 async 函数内部的异步操作执行完,才会执行 then 方法指定的回调函数。
下面是一个例子:
async function getTitle(url) { let response = await fetch(url); let html = await response.text(); return html.match(/<title>([\s\S]+)<\/title>/i)[1]; } getTitle('https://tc39.github.io/ecma262/').then(console.log('完成')) // "ECMAScript 2017 Language Specification"
上面代码中,函数 getTitle 内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,才会执行 then 方法里面的 console.log。
- 在 vue 中,我们可能要先获取 token ,之后再用 token 来请求用户数据什么的,可以这样子用:
methods:{ getToken() { return new Promise((resolve, reject) => { this.$http.post('/token') .then(res => { if (res.data.code === 200) { resolve(res.data.data) } else { reject() } }) .catch(error => { console.error(error); }); }) }, getUserInfo(token) { return new Promise((resolve, reject) => { this.$http.post('/userInfo',{ token: token }) .then(res => { if (res.data.code === 200) { resolve(res.data.data) } else { reject() } }) .catch(error => { console.error(error); }); }) }, async initData() { let token = await this.getToken() this.userInfo = this.getUserInfo(token) }, }
12. import 和 export
import 导入模块、export 导出模块 // example2.js // 导出默认, 有且只有一个默认 export default const example2 = { name : 'my name', age : 'my age', getName = function(){ return 'my name' } } //全部导入 // 名字可以修改 import people from './example2.js' -------------------我是一条华丽的分界线--------------------------- // example1.js // 部分导出 export let name = 'my name' export let age = 'my age' export let getName = function(){ return 'my name'} // 导入部分 // 名字必须和 定义的名字一样。 import {name, age} from './example1.js' //有一种特殊情况,即允许你将整个模块当作单一对象进行导入 //该模块的所有导出都会作为对象的属性存在 import * as example from "./example1.js" console.log(example.name) console.log(example.age) console.log(example.getName()) -------------------我是一条华丽的分界线--------------------------- // example3.js // 有导出默认, 有且只有一个默认,// 又有部分导出 export default const example3 = { birthday : '2018 09 20' } export let name = 'my name' export let age = 'my age' export let getName = function(){ return 'my name'} // 导入默认与部分 import example3, {name, age} from './example1.js'
总结:
1.当用 export default people 导出时,就用 import people 导入(不带大括号) 2.一个文件里,有且只能有一个 export default。但可以有多个 export。 3.当用 export name 时,就用 import { name }导入(记得带上大括号) 4.当一个文件里,既有一个 export default people, 又有多个 export name 或者 export age 时,导入就用 import people, { name, age } 5.当一个文件里出现 n 多个 export 导出很多模块,导入时除了一个一个导入,也可以用 import * as example
13. Class
对于 Class ,小汪用在 react 中较多。
13.1基本用法:
//定义类 class FunSum { constructor(x, y) { this.x = x; this.y = y; } sum() { console.log( this.x +this.y') } } // 使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。 let f = new FunSum(10, 20); f.sum() // 30
13.2 继承
class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 调用父类的constructor(x, y) this.color = color; } toString() { return this.color + ' ' + super.toString(); // 调用父类的toString() } }
上面代码中,constructor 方法和 toString 方法之中,都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的 this 对象。
子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类自己的 this 对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用 super 方法,子类就得不到 this 对象。
class Point { /* ... */ } class ColorPoint extends Point { constructor() { } } let cp = new ColorPoint(); // ReferenceError
上面代码中,ColorPoint 继承了父类 Point,但是它的构造函数没有调用 super 方法,导致新建实例时报错。