异步编程
概述
JS执行环境中负责执行代码的线程只有一个,同一时间只能执行一个任务,为解决耗时任务阻断代码执行问题,JavaScript将任务的执行模式分为两种:
- 同步模式(Synchronous)
- 异步模式(Asynchronous)
同步模式
同步模式指的是代码当中的任务将会按照代码编写顺序依次执行,后一个任务必须等待前一个任务执行完毕才会执行。
console.log('first');
const fun2 = () => {
console.log('second');
}
const fun3 = () => {
fun2();
console.log('third');
}
fun3();
console.log('end');
//结果是
=> first
=> second
=> third
=> end
这里的代码是一步接一步往下执行
弊端:当代码中存在耗时较长的任务时,将会阻塞代码的执行,此时可以使用异步模式来解决这一问题。
异步模式
异步模式指代码执行过程中,不会等待一个任务执行完成再执行另一个任务,任务开启过后就立即往后执行下一个任务,耗时任务的后续逻辑一般会通过回调函数的方式定义,当耗时任务完成后就会自动执行回调函数。
console.log('begin');
setTimeout(() => {
console.log('time1');
}, 1800);
setTimeout(() => {
console.log('time2');
setTimeout(() => {
console.log('time2 inner');
}, 800);
}, 1000);
console.log('end');
//结果是
=> begin
=> end
=> time2
=> time1
=> time2 inner
Promise
概述
Promise是异步编程的一种解决方案,Promise简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise状态:
-
Pending:等待(进行中)
-
Fulfilled:成功(已完成)
-
Rejected:失败
Promise特性:
- Promise的状态不受外界影响,只有异步操作的结果可以决定当前状态
- Promise的状态只能变更一次,一旦改变,状态就凝固了,不会再变,任何时候都可以得到这个结果。Promise的状态,只能由
Pending
->Fulfilled
或者从Pending
->Rejected
。Promise中使用resolve()
和reject()
函数来改变状态。
Promise优缺点:
-
优点:通过链式调用,让异步代码可以以同步操作的流程表示出来。解决了代码深层嵌套回调地狱的问题,使代码更加整洁更好维护
-
缺点:
- 无法取消
Promise
,一旦新建它就会立即执行,无法中途取消。 - 如果不设置回调函数(未定义catch捕获异常),
Promise
内部抛出的错误,不会反应到外部。 - 当处于
pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
- 无法取消
Promise基本使用
function ajax(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('Get', url)
xhr.responseType = 'json'
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response) //成功,返回数据
} else {
reject(new Error(this.statusText)) //失败,返回错误信息
}
}
xhr.send()
})
}
ajax('/api/user.json')
.then(res => {
// 成功
})
.catch(err => {
// 失败
})
Promise静态方法
-
Promise.resolve()
此方法有一个可选的参数,参数的类型会影响它的返回值,具体可分为三种情况(如下所列),其中有两种情况会创建一个新的已处理的Promise实例,还有一种情况会返回这个参数。
(1)当参数为空或非thenable时,返回一个新的状态为fulfilled的Promise。
(2)当参数为thenable时,返回一个新的Promise,而它的状态由自身的then()方法控制。
(3)当参数为Promise时,将不做修改,直接返回这个Promise。
// Promise中的静态方法 Promise.resolve // 1、参数为空 返回状态为fulfilled的新Promise,回调中接收传入参数 Promise.resolve().then(res => { console.log(res); //undefined }) // 2、参数为非thenable的值 返回状态为fulfilled的新Promise,回调中接收传入参数 Promise.resolve([1, 2, 3]).then(res => { console.log(res); //[1,2,3] }) // 3、参数为thenable 根据thenable内部返回值决定Promise状态,并返回结果 Promise.resolve({ then(resolve, reject) { let x=Math.random()*10 x>5?resolve('thenable resolved'):reject(new Error('thenable rejected')) } }).then(res => { console.log('res===>',res); }).catch(err=>{ console.log('err===>',err); }) // 4、参数为Promise 直接返回该Promise Promise.resolve(new Promise((resolve,reject)=>{ resolve('Get Promise') })).then(res=>{ console.log(res); //Get Promise })
-
Promise.reject()
此方法能接收一个参数,表示拒绝理由,它的返回值是一个新的已拒绝的Promise实例。与Promise.resolve()不同,Promise.reject()中所有类型的参数都会原封不动的传递给后续的已拒绝的回调函数。
// Promise中的静态方法 Promise.reject Promise.reject().catch(err => { console.log(err); //undefined }) Promise.reject([1, 2, 3]).catch(err => { console.log(err); //[1,2,3] }) let thenable = { then(resolve, reject) { resolve('thenable resolved') } } Promise.reject(thenable) .then(res => { console.log('res==>', res); //不输出,reject返回的promise状态为rejected }) .catch(err => { console.log('err==>', err); //err==> { then: [Function: then] } }) let p1 = new Promise((resolve, reject) => { resolve('Get Promise') }) Promise.reject(p1) .catch(err => { console.log(err); //Promise { 'Get Promise' } console.log(err===p1); //true })
-
Promise.all()
此方法和接下来要讲解的Promise.race()都可用来监控多个Promise,当它们的状态发生变化时,这两个方法会给出不同的处理方式。
Promise.all()能接收一个可迭代对象,其中可迭代对象中的成员必须是Promise,如果是字符串、thenable等非Promise的值,那么会自动调用Promise.resolve()转换成Promise。Promise.all()的返回值是一个新的Promise实例,当参数中的成员为空时,其状态为fulfilled;而当参数不为空时,其状态由可迭代对象中的成员决定,具体分为两种情况。
(1)当可迭代对象中的所有成员都是已完成的Promise时,新的Promise的状态为fulfilled。而各个成员的决议结果会组成一个数组,传递给后续的已完成的回调函数。
(2)当可迭代对象中的成员有一个是已拒绝的Promise时,新的Promise的状态为rejected。并且只会处理到这个已拒绝的成员,接下来的成员都会被忽略,其决议结果会传递给后续的已拒绝的回调函数。
// Promise中的静态方法 Promise.all let p1 = Promise.resolve() let p2 = 'resolved' let p3 = Promise.reject('rejected') // 所有成员均为fulfilled,返回fulfilled,将结果数组传递给成功回调 Promise.all([p1, p2]) .then(res => { console.log('res', res); //res [ undefined, 'resolved' ] }) .catch(err => { console.log('err', err); }) // 成员中存在rejected,返回rejected,将失败原因传递给失败回调 Promise.all([p1, p3]) .then(res => { console.log('res', res); }) .catch(err => { console.log('err', err); //err rejected })
-
Promise.race()
(1)能接收一个可迭代对象,如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则
Promise.race
将解析为迭代中找到的第一个值。(2)可迭代对象成员必须是Promise,非Promise的值,会自动调用Promise.resolve()转换成Promise。
(3)返回值是一个新的Promise实例。
返回的新的Promise实例的状态与方法的参数有关,当参数的成员为空时,其状态为pending,则返回的 promise 将永远等待;当参数不为空时,其状态是最先被处理的成员的状态,并且此成员的决议结果会传递给后续相应的回调函数,如下代码所示。
// Promise中的静态方法 Promise.race var p1 = new Promise(function (resolve, reject) { setTimeout(resolve, 500, "one"); }); var p2 = new Promise(function (resolve, reject) { setTimeout(resolve, 100, "two"); }); Promise.race([p1, p2]).then(function (value) { console.log(value); // "two" // 两个都完成,但 p2 更快 }); var p3 = new Promise(function (resolve, reject) { setTimeout(resolve, 100, "three"); }); var p4 = new Promise(function (resolve, reject) { setTimeout(reject, 500, "four"); }); Promise.race([p3, p4]).then(function (value) { console.log(value); // "three" // p3 更快,所以它完成了 }, function (reason) { // 未被调用 }); var p5 = new Promise(function (resolve, reject) { setTimeout(resolve, 500, "five"); }); var p6 = new Promise(function (resolve, reject) { setTimeout(reject, 100, "six"); }); Promise.race([p5, p6]).then(function (value) { // 未被调用 }, function (reason) { console.log(reason); // "six" // p6 更快,所以它失败了 });
Generator
概述
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function
关键字与函数名之间有一个星号;二是,函数体内部使用yield
表达式,定义不同的内部状态(yield
在英语里的意思就是“产出”)。
Generator基本使用
function* gen(){
yield 1;
yield 2;
return 'end';
}
const g=gen()
console.log(g.next()) //{value:1,done:false}
console.log(g.next()) //{value:2,done:false}
console.log(g.next()) //{value:'end',done:true}
console.log(g.next()) //{value:undefined,done:true}
上面代码定义了一个 Generator 函数gen
,它内部有两个yield
表达式(1
和2
),即该函数有三个状态:1,2和 return 语句(结束执行)。
调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象。
必须调用遍历器对象的next
方法,使得指针移向下一个状态。也就是说,每次调用next
方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield
表达式(或return
语句)为止。换言之,Generator 函数是分段执行的,yield
表达式是暂停执行的标记,而next
方法可以恢复执行。
总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next
方法,就会返回一个有着value
和done
两个属性的对象。value
属性表示当前的内部状态的值,是yield
表达式后面那个表达式的值;done
属性是一个布尔值,表示是否遍历结束。
yield表达式
由于 Generator 函数返回的遍历器对象,只有调用next
方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield
表达式就是暂停标志。
遍历器对象的next
方法的运行逻辑如下。
(1)遇到yield
表达式,就暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。
(2)下一次调用next
方法时,再继续往下执行,直到遇到下一个yield
表达式。
(3)如果没有再遇到新的yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。
(4)如果该函数没有return
语句,则返回的对象的value
属性值为undefined
。
需要注意的是,yield
表达式后面的表达式,只有当调用next
方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
function* gen() {
yield 123 + 456;
}
const g=gen()
console.log(g.next()) //{value: 579, done: false}
上面代码中,yield
后面的表达式123 + 456
,不会立即求值,只会在next
方法将指针移到这一句时,才会求值。
next方法的参数
yield
表达式本身没有返回值,或者说总是返回undefined
。next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值。
function* f() {
for(var i = 0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}
var g = f();
g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
上面代码先定义了一个可以无限运行的 Generator 函数f
,如果next
方法没有参数,每次运行到yield
表达式,变量reset
的值总是undefined
。当next
方法带一个参数true
时,变量reset
就被重置为这个参数(即true
),因此i
会等于-1
,下一轮循环就会从-1
开始递增。