文章目录
1. 重点提炼
- 异步基础知识
- ajax原理
- callbackhell
- Promise
- Generator
2. 异步操作必备知识
- JS是单线程的
- 同步任务 与 异步任务
- Ajax原理
- Callback Hell
JS
是单线程 => 如在超市中买完东西,需要结账,在柜台前面顾客排成一列,正常情况下同一时间,柜台上的服务员,只能为一个顾客结账,等上一位顾客结账完毕后,则下一位顾客再结账。单线程,即同一时间内,只能处理一个任务。
那js
为啥设计成单线程语言呢?这和js
的用途是有关的,实际在做前端开发的时候,主要用到的最基本的三种技术,即为html
=> 布局及内容等待、css
=> 样式、js
=> 与用户交互和dom
操作等。这决定了它只能是单线程,否则会带来很复杂的同步问题。如果设置成多线程语言,操作dom
某个节点时,一个线程对dom
节点进行增加操作,同一时间,另一个线程对dom
节点进行删除操作,对于网页而言到底是增加还是删除,很难处理。因此为了避免这些复杂的问题和矛盾,js
的作者在设计初期,将js
语言设计成单线程的。
如果前一个js
任务耗时特别长,那么后一个任务不得不一直等着,因此js
作者将任务分为两类 =>
同步任务
与 异步任务
- 同步:只有前一个任务执行完毕,才能执行后一个任务
- 异步:当同步任务执行到某个
WebAPI
时,就会触发异步操作,此时浏览器会单独开线程去处理这些异步任务。
const a = 2
const b = 3
console.log("同步任务",a + b) // 同步任务
// 异步任务
setTimeout(() => {
console.log(a + b)
}, 1000)
console.log('异步任务')
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.89
Branch: branch02commit description:a2.89(异步操作必备知识——同步任务 与 异步任务)
tag:a2.89
现在大部分项目都是前后端数据分离
,前端的页面显示,前端需要发送ajax
请求后端的数据。但这个请求,不会立马完成,这有很多因素,如网络因素,并且后端需要进行数据库操作等,都需要耗费实际,因此它也是异步操作。
常考面试题 =>
先执行同步操作。
注意:定时器最小时间也是4
毫秒,因此不会是0
的,虽然设置的是0
,但执行时间必然大于等于4
毫秒。
// 1 3 2
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
console.log(3)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.90
Branch: branch02commit description:a2.90(异步操作必备知识——常考面试题)
tag:a2.90
任务进入执行栈,首先判断任务是同步的?还是异步的?
如果是异步则先进入Event Table
中,紧接着在事件队列中,队列是先进先出的。如定时器时间到了,则该方法从事件表中进入事件队列
中,在队列中排队等待,等主线程
任务执行完毕,即主线程空闲
,轮到事件队列
的任务,再执行。
伪代码 =>
实际task
是在2s
后执行吗?
并不会,因为首先进入事件表,再过2s
后进入事件队列,假设还有一个很复杂的同步任务,需要执行5s
,执行到2s
的时候,task
任务才进入到事件队列了,它得等3s
,主线程执行完这个复杂的同步任务,task
才能进入主线程进行执行。
因此定时器的时间未必是准确的。
// 伪代码
setTimeout(()=>{
task() // 表示一个任务
}, 2000)
sleep(5000) // 表示一个很复杂的同步任务,要执行很长时间
3. Ajax原理与Callback Hell
Ajax
=> 是指一种创建交互式、快速动态网页应用的网页开发技术,无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax
可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
不刷新页面的情况下,对页面的局部进行部分刷新,即局部刷新。
Ajax
原理 => 原生js
代码实现
1、创建XMLHttpRequest
对象
2、发送请求
3、服务端响应
// Ajax的原理
function ajax(url, callback) {
// 1、创建XMLHttpRequest对象
var xmlhttp
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest() // IE7之后的版本
} else { // 兼容早期浏览器
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
}
// 2、发送请求
xmlhttp.open('GET', url, true)
xmlhttp.send()
// 3、服务端响应
xmlhttp.onreadystatechange = function () {
// 正确返回
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
var obj = JSON.parse(xmlhttp.responseText)
// console.log(obj)
callback(obj)
}
}
}
// var url = 'http://abcxsdsa.com'
// ajax(url, res => {
// console.log(res)
// })
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.91
Branch: branch02commit description:a2.91(Ajax原理与Callback Hell——Ajax原理 => 原生js代码实现)
tag:a2.91
callback hell
=>
如发送三个ajax
请求,它们是有顺序依赖关系,但是ajax
请求是异步操作。
因此需要在第一次请求的回调函数中进行第二次请求,然后依次下去。但是如果是更多请求依赖,就导致了回调地狱
,代码结构越来越深,代码可读性极差,如果逻辑复杂,导致代码可维护性很差。 => es6
提出了Promise
,做了相关优化。
利用JSON
文件,模拟ajax
请求。
// 1 -> 2 -> 3
// callback hell
ajax('static/a.json', res => {
console.log(res)
ajax('static/b.json', res => {
console.log(res)
ajax('static/c.json', res => {
console.log(res)
})
})
})
如果嵌套变多,代码层次就会变深,维护难度也随之增加。
这就被称为 “回调地狱”
或者“回调深渊”
。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.92
Branch: branch02commit description:a2.92(Ajax原理与Callback Hell——callback hell )
tag:a2.92
4. 异步编程解决方案Promise
Promise
就是为了解决“回调地狱”
问题的,它可以将异步操作的处理变得很优雅。回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象promise
可以支持多个并发的请求,获取并发请求中的数据这个promise
可以解决异步的问题,本身不能说promise
是异步的。
Promise
接收一个回调函数
这里一共对应两个参数 => resolve
和reject
。它们是两个函数,由 JavaScript
引擎提供,不用自己部署。
resolve
=> 异步操作执行成功的一个回调函数
reject
=> 异步操作执行失败的一个回调函数
两者都可对then
传参。
- 处理结果正常的话,调用
resolve
(处理结果值),将Promise
对象的状态从“未完成”变为“成功”(即从pending
变为resolved
),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去 - 处理结果错误的话,调用
reject
(Error
对象),将Promise
对象的状态从“未完成”变为“失败”(即从pending
变为rejected
),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数。
Promise
的精髓 => 异步操作的状态管理
在项目中会加入异步逻辑,如何进行成功和失败的返回。
const promise = new Promise(function(resolve, reject) {
// ... some code
if ( /* 异步操作成功 */ ) {
resolve(value)
} else {
reject(error)
}
})
promise.then(function(value) {
// success
}, function(error) {
// failure
})
返回成功 =>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('123')
resolve('成功')
}, 1000)
}).then(res => {
console.log(res)
}, err => {
console.log(err)
})
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.93
Branch: branch02commit description:a2.93(异步编程解决方案Promise——返回成功)
tag:a2.93
返回失败 =>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('123')
reject('失败')
}, 1000)
}).then(res => {
console.log(res)
}, err => {
console.log(err)
})
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.94
Branch: branch02commit description:a2.94(异步编程解决方案Promise——返回失败)
tag:a2.94
注意:Promise
中的回调函数里是同步代码会立即执行,所以Promise
里的代码不一定是等一会再执行。
let p = new Promise((resolve, reject) => {
console.log(1)
resolve()
})
console.log(2)
p.then(res => {
console.log(3)
})
同步代码立即执行,所以先输出1
、2
,then
相当于跟着new Promise
的微任务,即理解为先执行正常的代码后,再执行then
相关代码。
1
2
3
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.95
Branch: branch02commit description:a2.95(异步编程解决方案Promise——笔试题1)
tag:a2.95
Promise
对应三种状态,状态不可逆
new
的时候 => pending
(进行中)
resolve
=>fulfilled / resolved
(成功)
reject
=> rejected
(失败)
注意
状态转化是单向的,不可逆转,已经确定的状态(
fulfilled/rejected
)无法转回初始状态(pending
),而且只能是从pending
到fulfilled
或者rejected
4.1 常用方法
4.1.1 Promise.prototype.then()
promise.then(onFulfilled, onRejected)
var promise = new Promise(function(resolve, reject) {
resolve('传递给then的值')
})
promise.then(function(value) {
console.log(value)
}, function(error) {
console.error(error)
})
以上代码创建一个 Promise
对象,定义了处理 onFulfilled
和 onRejected
的函数(handler
),然后返回这个 Promise
对象。
这个 Promise
对象会在变为 resolve
或者 reject
的时候分别调用相应注册的回调函数。
- 当
handler
返回一个正常值的时候,这个值会传递给Promise
对象的onFulfilled
方法。 - 定义的
handler
中产生异常的时候,这个值则会传递给Promise
对象的onRejected
方法。
4.1.2 Promise.prototype.catch()
捕获异常是程序质量保障最基本的要求,可以使用 Promise
对象的 catch
方法来捕获异步操作过程中出现的任何异常。
function test() {
return new Promise((resolve, reject) => {
reject(new Error('es'))
})
}
test().catch((e) => {
console.log(e.message) // es
})
以上代码展示了如何使用 catch
捕获 Promise
对象中的异常,有人会会问 catch
捕获的是 Promise
内部的 Error
还是 Reject
?上面的示例既用了 reject
也用了 Error
,到底是哪个触发的这个捕获呢?
function test() {
return new Promise((resolve, reject) => {
throw new Error('wrong')
})
}
test().catch((e) => {
console.log(e.message) // wrong
})
以上代码对比着上个代码就能明显感受出来的,throw Error
和 reject
都触发了 catch
的捕获,而第一个用法中虽然也有 Error
但是它不是 throw
,只是 reject
的参数是 Error
对象,换句话说 new Error
不会触发 catch
,而是 reject
。
注意
不建议在
Promise
内部使用throw
来触发异常,而是使用reject(new Error())
的方式来做,因为throw
的方式并没有改变Pronise
的状态
4.1.3 code
let p1 = new Promise((resolve, reject) => {
resolve(1)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 1000)
})
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(3)
}, 1000)
})
console.log(p1) // resolved
console.log(p2) // pending
console.log(p3) // pending
因为对状态没进行处理,所以浏览器报错,一会补上then
、catch
即可。
浏览器已经运行到1s
后了,因此PromiseState
里不是pending
(在1s
前,PromiseState
里是pending
),而是对应的resolved
/ fulfilled
/ rejected
了。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.96
Branch: branch02commit description:a2.96(异步编程解决方案Promise——状态)
tag:a2.96
rejected
可以通过then
的第二个参数(回调函数),或者.catch
获取 =>
let p1 = new Promise((resolve, reject) => {
resolve(1)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 1000)
})
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(3)
}, 1000)
})
console.log(p1) // resolved
console.log(p2) // pending
console.log(p3) // pending
setTimeout(() => {
console.log(p2)
}, 2000)
setTimeout(() => {
console.log(p3)
}, 2000)
p1.then(res => {
console.log(res)
})
p2.then(res => {
console.log(res)
})
p3.catch(err => {
console.log(err)
})
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.97
Branch: branch02commit description:a2.97(异步编程解决方案Promise——then与catch)
tag:a2.97
Promise
状态不可逆
let p = new Promise((resolve, reject) => {
reject(2)
resolve(1)
})
p.then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
只要promise
状态从pending
变为resolved
/ fulfilled
和 rejected
,就定型了,状态确定下来后就不可改变,即不可能再发生状态变化了。
2
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.98
Branch: branch02commit description:a2.98(异步编程解决方案Promise——Promise状态不可逆)
tag:a2.98
Promise
改造Ajax
的回调地狱请求 =>
回调地狱 => 链式操作,注意一定要返回Promise
对象,下一次的操作建立在上一次的promise
对象上。
successCallback && successCallback(obj)
=> 判断当前参数是否传递,如果传递了则调用。
function ajax(url, successCallback, failCallback) {
// 1、创建XMLHttpRequest对象
var xmlhttp
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest()
} else { // 兼容早期浏览器
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
}
// 2、发送请求
xmlhttp.open('GET', url, true)
xmlhttp.send()
// 3、服务端响应
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
var obj = JSON.parse(xmlhttp.responseText)
// console.log(obj)
successCallback && successCallback(obj)
} else if (xmlhttp.readyState === 4 && xmlhttp.status === 404) {
failCallback && failCallback(xmlhttp.statusText)
}
}
}
new Promise((resolve, reject) => {
ajax('static/a.json', res => {
console.log(res)
resolve()
})
}).then(res => {
console.log('a成功')
return new Promise((resolve, reject) => {
ajax('static/b.json', res => {
console.log(res)
resolve()
})
})
}).then(res => {
console.log('b成功')
return new Promise((resolve, reject) => {
ajax('static/c.json', res => {
console.log(res)
resolve()
})
})
}).then(res => {
console.log('c成功')
})
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a2.99
Branch: branch02commit description:a2.99(异步编程解决方案Promise——Promise改造Ajax的回调地狱请求)
tag:a2.99
以上写法有些笨拙,可再进行优化 =>
实际promise
的逻辑里,只有url
不太一样,因此可以封装成方法。
function getPromise(url) {
return new Promise((resolve, reject) => {
ajax(url, res => {
resolve(res)
}, err => {
reject(err)
})
})
}
getPromise('static/a.json')
.then(res => {
console.log(res)
return getPromise('static/b.json')
}).then(res => {
console.log(res)
return getPromise('static/c.json')
}).then(res => {
console.log(res)
})
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.01
Branch: branch02commit description:a3.01(异步编程解决方案Promise——Promise请求封装成方法)
tag:a3.01
加上失败的promise
=>
function ajax(url, successCallback, failCallback) {
// 1、创建XMLHttpRequest对象
var xmlhttp
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest()
} else { // 兼容早期浏览器
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
}
// 2、发送请求
xmlhttp.open('GET', url, true)
xmlhttp.send()
// 3、服务端响应
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
var obj = JSON.parse(xmlhttp.responseText)
// console.log(obj)
successCallback && successCallback(obj)
} else if (xmlhttp.readyState === 4 && xmlhttp.status === 404) {
failCallback && failCallback(xmlhttp.statusText)
}
}
}
function getPromise(url) {
return new Promise((resolve, reject) => {
ajax(url, res => {
resolve(res)
}, err => {
reject(err)
})
})
}
getPromise('static/aa.json')
.then(res => {
console.log(res)
return getPromise('static/b.json')
}, err => {
console.log(err)
return getPromise('static/b.json')
}).then(res => {
console.log(res)
return getPromise('static/c.json')
}).then(res => {
console.log(res)
})
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.02
Branch: branch02commit description:a3.02(异步编程解决方案Promise——加上失败的promise )
tag:a3.02
如果a
读取失败,不对它进行单独处理,而是统一处理,可再最后加入catch
。
getPromise('static/aa.json')
.then(res => {
console.log(res)
return getPromise('static/b.json')
}).then(res => {
console.log(res)
return getPromise('static/c.json')
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
getPromise('static/a.json')
.then(res => {
console.log(res)
return getPromise('static/bb.json')
}).then(res => {
console.log(res)
return getPromise('static/c.json')
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
getPromise('static/a.json')
.then(res => {
console.log(res)
return getPromise('static/b.json')
}).then(res => {
console.log(res)
return getPromise('static/c.json')
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.03
Branch: branch02commit description:a3.03(异步编程解决方案Promise——异步出错统一处理,最后加catch)
tag:a3.03
5. Promise的静态方法
- Promise.resolve()
- Promise.reject()
- Promise.all()
- Promise.race()
5.1 Promise.resolve()
一般情况下我们都会使用 new Promise()
来创建 Promise
对象,但是除此之外我们也可以使用其他方法。
使用 Promise.resolve
和 Promise.reject
这两个方法。
静态方法 Promise.resolve(value)
可以认为是new Promise()
方法的快捷方式。
比如 Promise.resolve('success')
可以认为是以下代码的语法糖。
在这段代码中的resolve('success')
会让这个 Promise
对象立即进入确定(即resolved
)状态,并将 ‘success’
传递给后面 then
里所指定的onFulfilled
函数。
方法 Promise.resolve(value)
的返回值也是一个 Promise
对象,所以我们可以像下面那样接着对其返回值进行.then
调用。
let p1 = Promise.resolve('success')
console.log(p1)
p1.then(res => {
console.log(res)
})
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.04
Branch: branch02commit description:a3.04(Promise的静态方法——Promise.resolve())
tag:a3.04
Promise.resolve
作为 new Promise()
的快捷方式,在进行 Promise
对象的初始化或者编写测试代码的时候都非常方便。
5.2 Promise.reject()
Promise.reject(error)
是和 Promise.resolve(value)
类似的静态方法,是 new Promise()
方法的快捷方式。
比如 Promise.reject('fail')
就是下面代码的语法糖形式。
let p2 = Promise.reject('fail')
console.log(p2)
p2.catch(err => {
console.log(err)
})
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.05
Branch: branch02commit description:a3.05(Promise的静态方法——Promise.reject())
tag:a3.05
应用场景 => 有时没有promise
实例,也想调用then
和catch
方法,则需要Promise
静态方法包裹。
定义一个方法返回promise
,如果失败的时候,则返回一个字符串。则字符串下没有then
方法,可以包裹Promise
=> 利用Promise.reject
。
function foo(flag) {
if (flag) {
return new Promise(resolve => {
// 异步操作
resolve('success')
})
} else {
// return 'fail'
return Promise.reject('fail')
}
}
foo(true).then(res => {
console.log(res)
})
foo(false).then(res => {
console.log(res)
}, err => {
console.log(err)
})
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.06
Branch: branch02commit description:a3.06(Promise的静态方法——应用场景 => 有时没有promise实例,也想调用then和catch方法,则需要Promise静态方法包裹。)
tag:a3.06
5.3 Promise.all()
Promise.all(promiseArray)
三个异步操作,希望三个异步操作都完成后,再做一些事情。
如在项目中,想上传一些图片到服务器上面,但是服务器提供的上传接口每次只能上传一张图片。这个时候就需要循环图片,一张一张的上传了。如三张图片,都上传完毕后,提示用户上传成功。那如何知道三张图片已经上传完毕了呢?这跟当时的网络情况、图片大小、服务器响应时间等诸多因素都有关。
Promise.all([…])
=> 参数对应一个数组,里每一个内容都对应一个Promise
对象。
作用:等数组中每一个promise
对象都执行完成后,再执行Promise.all
的then
方法。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(1)
resolve('1成功')
}, 2000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(2)
resolve('2成功')
}, 1000)
})
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(3)
resolve('3成功')
}, 3000)
})
Promise.all([p1, p2, p3]).then(res => {
console.log(res)
}, err => {
console.log(err)
})
Promise.all
生成并返回一个新的 Promise
对象,所以它可以使用 Promise
实例的所有方法。参数传递promise
数组中所有的 Promise
对象都变为resolve
的时候,该方法才会返回, 新创建的 Promise
则会使用这些 promise
的值。
得到的res
是一个上述三个promise
调用resolve
配置参数组成的数组。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.07
Branch: branch02commit description:a3.07(Promise的静态方法——Promise.all)
tag:a3.07
假设某一个异步失败了
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(1)
resolve('1成功')
}, 2000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(2)
reject('2失败')
}, 1000)
})
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(3)
resolve('3成功')
}, 3000)
})
Promise.all([p1, p2, p3]).then(res => {
console.log(res)
}, err => {
console.log(err)
})
Promise.all
参数数组中只要有一个对象返回状态是失败,则认为整个都是失败的,则进入catch
或者err
第二个参数中去。即如果参数中的任何一个promise
为reject
的话,则整个Promise.all
调用会立即终止,并返回一个reject
的新的 Promise
对象。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.08
Branch: branch02commit description:a3.08(Promise的静态方法——Promise.all对于rejected的情况)
tag:a3.08
5.4 Promise.race()
Promise.race(promiseArray)
与Promise.all
相对的是Promise.race
当数组其中的promise
对象,只要有一个完成,则认为整个都是完成的。而all
是所有都完成,才认为是都完成。
Promise.race
生成并返回一个新的 Promise
对象。
参数 promise
数组中的任何一个 Promise
对象如果变为 resolve
或者 reject
的话, 该函数就会返回,并使用这个 Promise
对象的值进行 resolve
或者 reject
。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(1)
resolve('1成功')
}, 2000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(2)
resolve('2成功')
}, 1000)
})
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(3)
resolve('3成功')
}, 3000)
})
Promise.race([p1, p2, p3]).then(res => {
console.log(res)
}, err => {
console.log(err)
})
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.09
Branch: branch02commit description:a3.09(Promise的静态方法——Promise.race)
tag:a3.09
如果有一个失败了。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(1)
resolve('1成功')
}, 2000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(2)
reject('2失败')
}, 1000)
})
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(3)
resolve('3成功')
}, 3000)
})
Promise.race([p1, p2, p3]).then(res => {
console.log(res)
}, err => {
console.log(err)
})
数组中对应的某个promise
对象失败了,则认为是都失败了。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.10
Branch: branch02commit description:a3.10(Promise的静态方法——Promise.race对于rejected的情况)
tag:a3.10
5.5 应用场景
promise.all
应用场景 =>
如有一个图片数组,里面存储了很多图片数据。
图片需要一张张的上传,当所有图片都上传完了以后,给用户一个提示,告诉他上传完成。
定义一个promise
数组是一个空值。
遍历图片数组,一个一个图片的上传,上传图片的过程是一个异步操作。每次上传一个图片都new
一个promise
对象,并将其添加到promise
数组中,在其中完成图片上传的操作。当图片上传以后调用resolve
。因为有三张图片,因此会new
出3个promise
对象。
通过Promise.all
,把promise
数组传入即可。
同时用户上传图片后后端(云存储)返回的url
通过resolve(url)
=> 传递给all
,然后通过res
数组获取,将每个图片的url
插入到数据库中。
const imgArr = ['1.jpg', '2.jpg', '3.jpg']
let promiseArr = []
imgArr.forEach(item => {
promiseArr.push(new Promise((resolve, reject) => {
// 图片上传的操作(省略)
resolve()
}))
})
Promise.all(promiseArr).then(res => {
// 插入数据库的操作
console.log('图片全部上传完成')
})
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.11
Branch: branch02commit description:a3.11(Promise的静态方法——promise.all应用场景)
tag:a3.11
promise.race
应用场景 =>
如果当前加载图片,图片有可能加载失败,需要给图片加载设置一个超时时间(2s
)。
使用race
,因为要么图片加载成功,要么定时器到达时间。
function getImg() {
// 图片加载是异步过程 放入promise
return new Promise((resolve, reject) => {
let img = new Image()
// 图片加载成功
img.onload = function () {
resolve(img)
}
img.src = 'http://www.xxx.com/xx.jpg' // 失败的地址
})
}
function timeout() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 图片加载到时
reject('图片请求超时')
}, 2000)
})
}
Promise.race([getImg(), timeout()]).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.12
Branch: branch02commit description:a3.12(Promise的静态方法——promise.race应用场景-图片请求超时)
tag:a3.12
function getImg() {
// 图片加载是异步过程 放入promise
return new Promise((resolve, reject) => {
let img = new Image()
// 图片加载成功
img.onload = function () {
resolve(img)
}
// img.src = 'http://www.xxx.com/xx.jpg' // 失败的地址
img.src = 'https://avatar.csdnimg.cn/8/1/4/3_u013946061.jpg' // 成功的地址
})
}
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.13
Branch: branch02commit description:a3.13(Promise的静态方法——promise.race应用场景-图片请求成功)
tag:a3.13
promise
不仅可以解决回调地狱的问题,还可以处理多个并发的请求,获取并发请求各种各样的数据,promise
对异步状态进行管理,它可以解决异步的问题,但promise
本身不是异步的,而是promise
里面可以写异步的操作,promise
可以很好的对这些异步状态进行管理。
6. 异步编程解决方案Generator
什么是 JavaScript Generators
呢?通俗的讲Generators
是可以用来控制迭代器的函数。它们可以暂停,然后在任何时候恢复。
普通的函数在执行过程中,不能再干别的事情。而Generator
函数,可以在函数执行的时候进行暂停,又可在暂停的地方继续执行。
Generator
函数 => 在function
后加*
,在其内存在关键字yield
。
Generator
函数,无法直接调用,需要手动执行它,需要用next
执行,有点类似调试程序时的单步执行。Generator
函数执行是可以暂停的,然后可以继续上一次暂停的状态。
function* foo() {
for (let i = 0; i < 3; i++) {
yield i
}
}
console.log(foo())
let f = foo()
console.log(f.next())
console.log(f.next())
console.log(f.next())
console.log(f.next())
该函数一共只能循环3
次,执行第四次实际已经执行完成了,所以value
是undefined
,即yield
后的表达式的值,done
为true
,代表函数已经执行完毕了。
常规的循环只能一次遍历完所有值,Generator
可以通过调用 next
方法拿到依次遍历的值,让遍历的执行变得“可控”
。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.14
Branch: branch02commit description:a3.14(异步编程解决方案Generator——Generator函数)
tag:a3.14
这个是 Generator
的定义方法,有几个点值得注意:
- **比普通函数多一个 ***
- 函数内部用
yield
(打断点)来控制程序的执行的“暂停” - 函数的返回值通过调用
next
来“恢复”程序执行
注意
Generator
函数的定义不能使用箭头函数,否则会触发SyntaxError
错误这些做法都是错误的❌。
let generator = * () => {} // SyntaxError let generator = () * => {} // SyntaxError let generator = ( * ) => {} // SyntaxError
Generator
函数实际不会立即执行,而是返回一个生成器迭代器的对象。
yield
关键字用来暂停和恢复一个生成器函数
当迭代器函数调用next
时,其实在其内部就会执行yield
后的语句为止,调用一次,执行一次yield
后的语句。再调用next
的时候,yield
后的语句继续执行。
next
函数返回一个对象,value
代表yield
后的表达式返回值,done
代表后续是否有yield
的语句。
注意:Generator
函数不能作为构造函数去使用,只能返回一个生成器迭代器的对象。yield
关键字只能在Generator
函数内使用。
function* gen(args) {
args.forEach(item => {
yield item + 1
})
}
报错了,yield
不能写在其他函数内,所以它在forEach
函数内不能使用。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.15
Branch: branch02commit description:a3.15(异步编程解决方案Generator——
yield
不能写在其他函数内)tag:a3.15
小结:
yield
表达式的返回值是undefined
,但是遍历器对象的next
方法可以修改这个默认值。
function* gen() {
let val
val = yield 1
console.log( `1:${val}` ) // 1:undefined
val = yield 2
console.log( `2:${val}` ) // 2:undefined
val = yield 3
console.log( `3:${val}` ) // 3:undefined
}
var g = gen()
console.log(g.next()) // {value: 1, done: false}
console.log(g.next()) // {value: 2, done: false}
console.log(g.next()) // {value: 3, done: false}
console.log(g.next()) // {value: undefined, done: true}
从这个代码可以看出来,yield
表达式的返回值是 undefined
。
yeild *
是委托给另一个遍历器对象或者可遍历对象
function* gen() {
let val
val = yield 1
console.log( `1:${val}` ) // 1:undefined
val = yield 2
console.log( `2:${val}` ) // 2:undefined
val = yield [3, 4, 5]
console.log( `3:${val}` ) // 3:undefined
}
Generator
对象的next
方法,遇到yield
就暂停,并返回一个对象,这个对象包括两个属性:value
和done
。
参考步骤1
的代码可以明确看出来,执行第一句 g.next
,gen
代码执行到 yield 1
,程序暂停,此时返回了一个对象:{value: 1, done: false}
经典面试题
function* gen(x) {
let y = 2 * (yield(x + 1))
let z = yield(y / 3)
return x + y + z
}
let g = gen(5)
console.log(g.next()) // 6
console.log(g.next()) // NaN
console.log(g.next()) // NaN
实际next
函数是可以传递参数的,参数是上一回Generator
中yield
语句(其后表达式)的返回值。(注意第一次调用next
,传递参数没有用处,因为此前没有yield
,主要根据调用函数时传递的参数)这里第二次调用next
时没有传参,因此2 * undefined
,y
值必然是NaN
,接着NaN / 3
,返回的表达式的值则为NaN
。第三次执行x + y + z
,同理还是NaN
。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.16
Branch: branch02commit description:a3.16(异步编程解决方案Generator——next没有参数的情况)
tag:a3.16
function* gen(x) {
let y = 2 * (yield(x + 1))
let z = yield(y / 3)
return x + y + z
}
let g = gen(5)
console.log(g.next()) // 6
console.log(g.next(12)) // y=24 8
console.log(g.next(13)) // z=13 x=5 y=24 42
第一次执行:5 + 1 = 6
第二次执行:2 * 12 / 3 = 8
(12
是上一回yeild
的值,即(yield(x + 1))
)
第三次执行:5 + 24+ 13 = 42
(13
是上一回yeild
的值,yield(y / 3)
)
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.17
Branch: branch02commit description:a3.17(异步编程解决方案Generator——next传参的情况)
tag:a3.17
我们经常玩一些小游戏,比如数数字,敲7
,到7
和7
的倍数,无限循环转圈去数数
从1
开始数数,数到7
的倍数则输出 =>
通过 Generator
我们就能轻松实现,只要调用 n.next
我们就知道下一个数是什么了,而使用普通函数却没法做到。
function* count(x = 1) {
while (true) {
if (x % 7 === 0) {
yield x
}
x++
}
}
let n = count()
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)
如果没有Generator
,则是死循环,是无法一直实现下去的。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.18
Branch: branch02commit description:a3.18(异步编程解决方案Generator——数7应用)
tag:a3.18
Generator
如何对异步状态进行管理呢?
function ajax(url, callback) {
// 1、创建XMLHttpRequest对象
var xmlhttp
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest()
} else { // 兼容早期浏览器
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP')
}
// 2、发送请求
xmlhttp.open('GET', url, true)
xmlhttp.send()
// 3、服务端响应
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
var obj = JSON.parse(xmlhttp.responseText)
// console.log(obj)
callback(obj)
}
}
}
function request(url) {
ajax(url, res => {
getData.next(res)
})
}
function* gen() {
let res1 = yield request('static/a.json')
console.log(res1)
let res2 = yield request('static/b.json')
console.log(res2)
let res3 = yield request('static/c.json')
console.log(res3)
}
let getData = gen()
getData.next()
getData.next()
调用,执行第一个yield
,在request
方法中,发送ajax
请求,再执行回调 => getData.next
,并将ajax
返回的res
赋值给res1
。然后依次往下执行。
参考:https://github.com/6xiaoDi/blog-ECMScript-Series/tree/a3.19
Branch: branch02commit description:a3.19(异步编程解决方案Generator——实现ajax请求)
tag:a3.19
6.1 常用方法小结
6.1.1 next([value])
Generator
对象通过 next
方法来获取每一次遍历的结果,这个方法返回一个对象,这个对象包含两个属性:value
和 done
。value
是指当前程序的运行结果,done
表示遍历是否结束。
next
是可以接受参数的,这个参数可以让你在 Generator
外部给内部传递数据,而这个参数就是作为上一个 yield
的返回值。
过程分析 =>
-
g.next(20)
这句代码会执行gen
内部的代码,遇到第一个yield
暂停。所以console.log('before ${val}')
执行输出了before 100
,此时的val
是100
,所以执行到yield val
返回了100
,注意yield val
并没有赋值给val
。 -
g.next(30)
这句代码会继续执行gen
内部的代码,也就是val = yield val
这句,因为next
传入了30
,所以yield val
这个返回值就是30
,因此val
被赋值30
,执行到console.log('return ${val}')
输出了30
,此时没有遇到yield
代码继续执行,也就是while
的判断,继续执行console.log('before ${val}')
输出了before 30
,再执行遇到了yield val
程序暂停。 -
g.next(40)
重复步骤2
。
function* gen() {
var val = 100
while (true) {
console.log( `before ${val}` )
val = yield val
console.log( `return ${val}` )
}
}
var g = gen()
console.log(g.next(20).value)
// before 100
// 100
console.log(g.next(30).value)
// return 30
// before 30
// 30
console.log(g.next(40).value)
// return 40
// before 40
// 40
6.1.2 return()
return
方法可以让 Generator
遍历终止,有点类似 for
循环的 break
。
function* gen() {
yield 1
yield 2
yield 3
}
var g = gen()
console.log(g.next()) // {value: 1, done: false}
console.log(g.return()) // {value: undefined, done: true}
console.log(g.next()) // {value: undefined, done: true}
从 done
可以看出代码执行已经结束。
当然 return
也可以传入参数,作为返回的 value
值。
function* gen() {
yield 1
yield 2
yield 3
}
var g = gen()
console.log(g.next()) // {value: 1, done: false}
console.log(g.return(100)) // {value: 100, done: true}
console.log(g.next()) // {value: undefined, done: true}
6.1.3 throw()
可以通过 throw
方法在 Generator
外部控制内部执行的**“终断”**。
function* gen() {
while (true) {
try {
yield 42
} catch (e) {
console.log(e.message)
}
}
}
let g = gen()
console.log(g.next()) // { value: 42, done: false }
console.log(g.next()) // { value: 42, done: false }
console.log(g.next()) // { value: 42, done: false }
// 中断操作
g.throw(new Error('break'))
console.log(g.next()) // {value: undefined, done: true}
7. 异步操作解决方案
- 回调
- promise
- Generator
- (es7)async和await
(后续待补充)