首先,明确一个概念,js是单线程的。 单线程如何异步?
js是一门事件驱动(event-driven)驱动执行的语言,这些语言的top-level不像Oc/java那样是 main,而是一组 event-handler。
js的调用栈是同步的(单线程,一次只能做一件事), 异步调用是把调用函数加入一个队列,等待主栈空闲。
下面这个例子是一个原始的异步调用写法,代码中执行了一个定时器,2s后调用传入的func函数,将asyncfun函数传入并执行
function asyncfun() {
console.log('asyncfun');
}
setTimeout(func, 2000);
console.log('task');
Promise 可以看作一个执行容器,有三种状态pending(进行中)、fulfilled(已成功)和rejected(已失败)
只能从pending状态切换到fulfilled或rejected
function asyncfunc() {
return 1
}
let mypromise = new Promise(function(onFulfilled, onRejected){
console.log("Promise started");
let result = asyncfunc()
if (result === 1) {
onFulfilled(result)
} else {
onRejected('result !== 1')
}
})
mypromise.then(function(result){
console.log(result);
}, function(error) {
console.error(error);
})
console.log('hello mypromise');
// Promise started
// hello mypromise
// 1
Promise一旦创建就会被立即执行,上述例子中,最先打印Promise started,接着打印hello mypromise,最后执行then方法中的函数打印resolve或reject传出来的值1
Promise的实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。
它的作用是为 Promise 实例添加状态改变时的回调函数。
then方法的第一个参数是onFulfilled状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
let myPromise = new Promise(function(onFulfilled, onRejected){})
let thenPromise = myPromise.then(function(onFulfilled){}, function(onRejected){})
console.log(myPromise); // Promise { }
console.log(thenPromise); // Promise { }
then() 函数返回的是一个Promise实例,通过打印myPromise和thenPromise可以印证
由于Promise的then()方法返回的是一个Promise实例,我们可以使用then()方法做链式调用
Promise容器中使用fetch进行异步网络请求,链式then()调用进行报文处理
// npm install node-fetch --save
import fetch from 'node-fetch'
function callback(data) {
console.log("data=", data);
}
function getGithubUser(callback) {
fetch('https://api.github.com/users/github') // fetch函数返回的实际是一个Promise对象
.then(function(response) {
// 报文处理
if (response.status === 200 ) {
return response.json()
} else {
return `code: ${response.status}`
}
}).then(function(json){
// 返回调用
callback(json)
})
console.log("fetch started");
}
getGithubUser(callback)
Promise实例的catch方法用于指定发生错误时的回调函数,使用效果上相当于是.then(null, onRejected)
let mypromise = new Promise((onFulfilled, onRejected) => {
onRejected('result !== 1')
})
mypromise.then(null, (error) => {
console.error(error); //result !== 1
})
mypromise.catch((error) => {
console.error(error); // result !== 1
})
let p1 = new Promise((resolved, rejected)=>{
resolved(1)
})
let p2 = new Promise((resolved, rejected)=>{
resolved(2)
})
let p3 = new Promise((resolved, rejected)=>{
resolved(3)
})
const p = Promise.all([p1, p2, p3]);
p.then(function (posts) {
console.log(posts); // [1,2,3]
}).catch(function(reason){
console.log(reason);
});
(1)只有p1、p2、p3的状态都变成resolved,p的状态才会变成resolved,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
Promise.race()
Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。 与all()不同的是,只要多个实例中一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给Promise.race()的回调函数。
网络请求设置超时时间实际场景应用举例:
import fetch from 'node-fetch'
const p1 = fetch('https://api.github.com/users/github')
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => reject('request timeout'), 50)
})
const p = Promise.race([p1, p2])
p.then((response)=>{
return response.json()
}).then((json)=>{
console.log(json);
}).catch((msg)=>{
console.log(msg);
})
以上代码中,设置超时时间50ms, Promise实例p中p2先一步p1执行完成,调用reject回调,所以将Promise实例p执行catch方法,打印"request timeout"; 如果将超时时间设置为5000ms(根据你的网络状态定义), 保证在5s内完成网络请求,将执行p1的resolve回调,打印报文
const p = Promise.resolve('foo')
// 等价于
// const p = new Promise(resolve => resolve('foo'))
p.then((result)=>{console.log(result);}) // foo
const p2 = Promise.reject('reject error')
// 等价于
// const p2 = new Promise((resolve, reject) => reject('reject error'))
p2.catch((error)=>{console.log(error)}) //reject error
import fetch from 'node-fetch'
function getFoo () {
return new Promise(function (resolve, reject){
resolve('foo');
});
}
function getGithubUser() {
return fetch('https://api.github.com/users/github') // fetch函数返回的实际是一个Promise对象
}
getFoo().then((response)=>{
console.log(response);
getGithubUser().then((response)=>{
console.log(response.status);
}, (error)=>{
console.log(error);
})
}, (error)=>{
console.log(error);
})
// foo
// 200
Generator 函数是 ES6 提供的一种异步编程的解决方案,通过分步执行函数的方式调度任务。
下面例子演示了一个简单的Generater函数的申明和调用,其中getData定义了4个yield表达式和1个return表达式
前4次被调用者使用next方法调用4次,返回不同的返回值和done:false
第5次以后调用时,返回5和done:true
第6次以后调用时,返回undefined和done:true
function* getData() {
console.log('getData start');
yield 1
yield 2
yield 3
yield 4
return 5
}
let generater = getData()
console.log(generater.next()); // getData start // { value: 1, done: false }
console.log(generater.next()); // { value: 2, done: false }
console.log(generater.next()); // { value: 3, done: false }
console.log(generater.next()); // { value: 4, done: false }
console.log(generater.next()); // { value: 5, done: true }
console.log(generater.next()); // { value: undefined, done: true }
next方法传参:传参替代上一次yield表达式的值
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
console.log(a.next()) // Object{value:6, done:false}
console.log(a.next()) // Object{value:NaN, done:false}
console.log(a.next()) // Object{value:NaN, done:true}
var b = foo(5);
console.log(b.next()); // { value:6, done:false }
console.log(b.next(12)); // { value:8, done:false } // y= 2*12 = 24
console.log(b.next(13)); // { value:42, done:true } // z= 13 x+y+z = 5+24+13=42
throw表达式例子
var g = function* () {
try {
yield -1;
yield 0;
yield 0;
} catch (e) {
console.log('generator catch exception:', e);
}
};
var i = g();
let resCode = i.next();
if (resCode.value === -1) {
i.throw('resCode.value = -1') // generator catch exception: resCode.value = -1
}
resCode = i.next();
console.log(resCode); // { value: undefined, done: true }
循环调用时,一旦next方法的返回对象的done属性为true
function* getData() {
yield 1
yield 2
yield 3
yield 4
return 5
}
for (let v of getData()) {
console.log(v); // 1, 2, 3, 4
}
function* numbers () {
yield 1
yield 2
return 3
yield 4
}
// 扩展运算符
let array = [...numbers()]
console.log(array); //[1, 2]
// Array.from 方法
let array1 = Array.from(numbers())
console.log(array1); //[1, 2]
// 解构赋值
let [x, y] = numbers();
console.log(x, y); // 1 2
// for...of 循环
for (let n of numbers()) {
console.log(n)
}
// 1
// 2
使用yield*表达式调用其它Generater函数
function getData() {
return 'data'
}
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo(); // 使用yield*表达式调用其它Generater函数
yield 'y';
yield getData()
}
for (let v of bar()){
console.log(v);
}
// x
// a
// b
// y
// data
import fetch from 'node-fetch'
function getFoo () {
return new Promise(function (resolve, reject){
resolve('foo');
});
}
function getGithubUser() {
return fetch('https://api.github.com/users/github') // fetch函数返回的实际是一个Promise对象
}
const g = function* () {
try {
const foo = yield getFoo();
console.log(foo);
const githubUser = yield getGithubUser()
console.log(githubUser.status);
} catch (e) {
console.log(e);
}
};
// 调用执行Generator函数获取结果
const it = g();
function runGenerator(result) {
if (result.done) {
return result.value;
} else {
return result.value.then(function (result) {
return runGenerator(it.next(result));
}, function (error) {
return runGenerator(it.throw(error));
});
}
}
runGenerator(it.next());
// foo
// 200
上面代码的Generator函数g中,有两个异步操作getFoo和getGithubUser,它们分别返回的就是一个Promise对象。函数runGenerator用来处理这个Promise对象,并调用下一个next方法。
Generator的最大优点,就是代码的写法非常像同步操作,如果去除yield命令,简直一模一样。
Redux技术栈中有个优秀的异步任务调度中间件redux-saga就是基于Generator语法特性实现
Async/Await应该是目前最简单的异步方案, async函数实际上是Generator函数的改进
Async/Await基本语法如下:
1.使用async修饰的函数表示这是一个异步函数,await只能用在这个函数里面; 2.使用async修饰的函数返回值是一个Promise对象 2.await 表示在这里等待promise返回结果了,再继续执行;
3.await 后面跟着的应该是一个promise对象(当然,其他返回值也没关系,只是会立即执行,不过那样就没有意义了)
下面的例子中,首先输出’start’,接着sleep 3s,接着输出’end’
var sleep = function (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve();
}, time);
})
};
var start = async function () {
// 在这里使用起来就像同步代码那样直观
console.log('start');
await sleep(3000);
console.log('end');
};
console.log(start());
// start
// Promise { } //async函数返回的是一个promise对象
// end
使用Async/Await来调度异步任务
import fetch from 'node-fetch'
function getFoo () {
return new Promise(function (resolve, reject){
resolve('foo');
});
}
function getGithubUser() {
return fetch('https://api.github.com/users/github') // fetch函数返回的实际是一个Promise对象
}
const asyncTask = async function() {
let foo = await getFoo();
console.log(foo);
let githubUser = await getGithubUser()
console.log(githubUser.status);
}
asyncTask()
console.log('asyncTask start');
// asyncTask start
// foo
// 200
在ES6之前,js中是通过函数生成实例对象
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2); //函数的构造函数用法
console.log(p.toString()); //(1, 2)
es6后我们可以使用class定义一个类, 然后通过类的构造函数实例对象
class PointClass {
// 构造方法constructor
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
let p = new PointClass(1, 2)
console.log(p.toString()); //(1, 2)
class PointClass {
// 构造方法constructor
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
console.log(typeof PointClass); // function
console.log(PointClass); // [Function: PointClass]
console.log(PointClass.prototype); // PointClass {}
console.log(PointClass.prototype.constructor); // [Function: PointClass]
PointClass.prototype.sayHello = ()=>{
console.log('hello');
}
p.sayHello() //hello
可以看到PointClass实际上是一个function,他的原型对象是PointClass{}
除了在类解构体中定义方法,还可以通过prototype和__proto__添加方法
class People {
constructor(name, sex) {
this.name = name
this.sex = sex
}
}
let p1 = new People('zhangsan', 'man')
let p2 = new People('lisi', 'woman')
console.log(p1.__proto__);
console.log(p2.__proto__);
console.log(People.prototype);
//为函数对象的原型对象定义方法
People.prototype.sayName = function() {
console.log(this.name);
}
//为普通对象的原型属性定义方法
p1.sayName() //zhangsan
p2.sayName() //lisi
p1.__proto__.saySex = function() {
console.log(this.sex);
}
p1.saySex() //man
p2.saySex() //women
class People {
constructor(name, sex) {
this.name = name
this.sex = sex
}
static classMethod() {
console.log('hello People class');
}
}
let p = new People()
p.classMethod() // TypeError: p.classMethod is not a function
People.classMethod() // hello People class
通过extends关键字继承
class People {
constructor(name, sex) {
this.name = name
this.sex = sex
}
getName() {
return this.name;
}
getSex() {
return this.sex;
}
}
class Student extends People {
constructor(name, sex, grade) {
super(name, sex)
this.grade = grade
}
getGrade() {
return this.grade
}
}
let s = new Student('zhangsan', 'man', 'grade 5')
console.log(s.getName(), s.getSex(), s.getGrade()); // zhangsan man grade 5
在上面例子中,Student的构造函数中使用了两个关键字,this和super,只有调用super之后,才可以使用this关键字,否则会报错。
class A {
constructor(name) {
this.name = name
}
getName() {
return this.name;
}
}
class B extends A {
constructor(name, grade) {
super(name)
this.grade = grade
}
getGrade() {
return this.grade
}
}
console.log(A); // [Function: A]
console.log(A.__proto__); //[Function]
console.log(A.prototype); // A {}
console.log(A.prototype.__proto__); // {}
console.log(B); //[Function: B]
console.log(B.__proto__); // [Function: A]
console.log(B.prototype); // B {}
console.log(B.prototype.__proto__); // A{}
let a = new B.__proto__('wangwu')
console.log(a.getName()); // wangwu
通过以上的例子中可以看出:
a).子类的__proto__属性,指向父类的构造函数
b).子类的prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性
实例的_proto_
子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性
class A {
constructor(name) {
this.name = name
}
getName() {
return this.name;
}
}
class B extends A {
constructor(name, grade) {
super(name)
this.grade = grade
}
getGrade() {
return this.grade
}
}
let a = new A('zhangsan')
let b = new B('lisi', 'grade 5')
console.log(a.__proto__); // A{}
console.log(b.__proto__); // B{}
console.log(b.__proto__.__proto__); // A{}