关于 JS 的异步
Promise 基本使用
log(new Promise((resolve, reject) => {})) //Promise {<pending>}
log(new Promise((resolve, reject) => {
resolve('yes') // Promise {<resolved>: "no"}
}))
log(new Promise((resolve, reject) => {
reject('no') // Promise {<rejected>: "no"}
}))
// 成功状态和拒绝状态如何处理?在后面使用 then ,then 接受两个回调函数(一个是成功回调,一个是失败回调)
new Promise((resolve, reject) => {
// resolve('yes')
reject('no')
}).then(value => {
console.log(value)
}, reason => {
console.log(reason)
})
宏任务和微任务执行顺序
图片加载、文件加载、定时器、网络请求这些属于宏任务,Promise 属于微任务。
// 案例1
setTimeout(() => {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('promise')
resolve()
}).then(value => {
console.log('then1')
return new Promise(resolve => {
console.log(1)
resolve()
})
}).then(value => {
console.log('then2')
})
console.log('main') // 这行属于同步任务
// 打印顺序如下
// promise > main > then1 > 1 > then2 > setTimeout
// 案例2
setTimeout(() => {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('promise')
resolve()
}).then(value => {
console.log('then1')
setTimeout(() => {
return new Promise(resolve => {
console.log(1)
resolve()
}).then(value => {
console.log('then3')
})
}, 0)
}).then(value => {
console.log('then2')
})
console.log('main')
// 打印顺序如下
// promise > main > then1 > then2 > setTimeout > 1 > then3
同步任务优先级最高,Promise 的回调函数也属于同步的,then 只有等到 promise 状态改变被通知后才加入到 微任务队列 里,同步任务(可以理解为主线程)执行完毕后去检测微任务队列并执行里面的微任务,微任务队列都执行完毕后,检测宏任务队列并执行里面的宏任务。
// 案例3
let promise = new Promise(resolve => {
setTimeout(() => {
console.log('setTimeout')
resolve()
}, 0)
console.log('promise')
}).then(value => {
console.log('then1')
setTimeout(() => {
return new Promise(resolve => {
console.log(1)
resolve()
})
}, 0)
}).then(value => {
console.log('then2')
})
console.log('main')
// 打印顺序如下
// promise > main > setTimeout > then1 > then2 > 1
Promise 单一状态和状态切换
let p1 = new Promise((resolve, reject) => {
// resolve('yes')
// reject('no')
setTimeout(() => {
reject('no')
}, 3000)
})
new Promise((resolve, reject) => {
resolve(p1)
reject('error') // 没有作用
}).then(ok => {
console.log(ok)
}, no => {
console.log(no)
})
// resolve 的值是 promise 的话会影响后面 then 的执行
// 结果:等待了三秒,打印出 no
// promise 状态改变后是不可逆的
then 的基本用法
new Promise((resolve, reject) => {
// resolve('yes')
reject('no')
})
.then() // 这里不接受回调,但是可以向后面传递
.then(null, reason => {
console.log(reason)
})
then 的返回值
then 方法返回的是一个 promise ,其状态根据 then 中的回调函数来决定。 [then 的返回值](Promise.prototype.then() - JavaScript | MDN (mozilla.org))
// 案例 4
let p1 = new Promise((resolve, reject) => {
resolve('ok')
})
let p2 = p1.then(ok => {
console.log(ok)
}, reason => {
console.log(reason)
})
setTimeout(() => {
console.log(p1)
console.log(p2)
})
// 结果如下
// ok > Promise {<resolved>: "ok"} > Promise {<resolved>}
then 返回其他类型的 Promise 封装
主要是实现:thenable 接口。
// 案例 1
let p1 = new Promise((resolve, reject) => {
resolve('ok1')
}).then(ok => {
console.log(ok)
return {
then(resolve, reject) {
console.log('ok2')
// resolve('ok3')
setTimeout(() => {
resolve('ok3')
}, 1000)
}
}
}, reason => {
console.log(reason)
}).then(ok => {
console.log(ok)
})
// 案例 2
let p1 = new Promise((resolve, reject) => {
resolve('ok1')
}).then(ok => {
console.log(ok)
class Then {
then(resolve, reject) {
console.log('ok2')
// resolve('ok3')
setTimeout(() => {
resolve('ok3')
}, 1000)
}
}
return new Then()
}, reason => {
console.log(reason)
}).then(ok => {
console.log(ok)
})
// 案例 3
let p1 = new Promise((resolve, reject) => {
resolve('ok1')
}).then(ok => {
console.log(ok)
return class {
static then(resolve, reject) {
console.log('ok2')
// resolve('ok3')
setTimeout(() => {
resolve('ok3')
}, 1000)
}
}
}, reason => {
console.log(reason)
}).then(ok => {
console.log(ok)
})
使用 Promise 实现 get 请求
function ajax(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('GET', url)
xhr.send()
xhr.onload = function() {
if(this.status === 200) {
resolve(JSON.parse(this.response))
} else {
reject('error')
}
}
xhr.onerror = function() {
reject('error')
}
})
}
Promise 错误处理与 catch
catch 返回值是 Promise,其状态根据catch 内的回调函数执行情况决定。
new Promise((resolve, reject) => {
resolve('ok')
// reject(new Error('fail'))
// throw new Error('fail')
// count + 1 // 系统自动抛出这行错误
/*
try {
count + 1
} catch(error) {
reject(error)
}
*/
})
.then(ok => {
console.log(ok)
return new Promise((resolve, reject) => {
reject('fail')
})
}, reason => {
console.log(reason)
})
.then(ok => {
console.log(ok)
})
.catch(err => {
console.log('error: ', err)
})
[MDN 文档描述 Promise catch](Promise.prototype.catch() - JavaScript | MDN (mozilla.org))
Promise 自定义错误处理
ajax(url)
.then(res => {
console.log(res)
})
.catch(err => {
if(err instanceof ParamError) {
console.log(err.msg)
}
if(err instanceof HttpError) {
alert(err.msg)
}
})
class ParamError extends Error {
constructor(msg) {
super(msg)
this.name = 'ParamError'
}
}
class HttpError extends Error {
constructor(msg) {
super(msg)
this.name = 'HttpError'
}
}
function ajax(url) {
return new Promise((resolve, reject) => {
if(!/^http/.test(url)) {
throw new ParamError('url 不合规')
}
let xhr = new XMLHttpRequest();
xhr.open('GET', url)
xhr.send()
xhr.onload = function() {
if(this.status === 200) {
resolve(JSON.parse(this.response))
} else if (this.status === 404) {
reject(new HttpError('参数有误'))
} else {
reject('error')
}
}
xhr.onerror = function() {
reject('error')
}
})
}
Promise.prototype.finally() (ES9/ES2018)
new Promise((resolve, reject) => {
reject('no')
})
.then(ok => {
console.log(ok)
})
.catch(err => {
console.log(err)
})
.finally(() => {
console.log('finally')
})
图片加载和 Promise 结合
function loadImg(src) {
return new Promise((resolve, reject) => {
const image = new Image()
image.src = src
image.onload = () => {
resolve(image)
}
image.onerror = reject
document.body.appendChild(image)
})
}
loadImage('xxx/xx.png').then(image => {
image.style.border = '2px solid red'
})
封装 定时器
function timeout(delay = 1000) {
return new Promise(resolve => setTimeout(resolve, delay))
}
timeout(2000).then(() => {
console.log('over1')
return timeout(300)
}).then(() => {
console.log('over2')
})
// ---------------------------- //
function interval(delay = 1000, cb) {
return new Promise(resolve => {
let id = setInterval(() => {
cb(id, resolve)
}, delay)
})
}
interval(2000, (timerId, resolve) => {
console.log('callback')
clearInterval(timerId)
resolve('finish1')
}).then(value => {
console.log(value)
return interval(100, (timerId, resolve) => {
clearInterval(timerId)
resolve('finish2')
})
}).then(res => {
console.log(res)
})
// 启发:使用 setInterval 实现 setTimeout 功能;
script 的异步加载
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = src
script.onload = () => resolve(script)
script.onerror = reject
document.body.appendChild(script)
})
}
loadScript('js/new-1.js').then(script => {
console.log(script)
return loadScript('js/new-2.js')
}).then(script => {
console.log(script)
})
Promise.resolve()
Promise.resolve(value)
方法返回一个以给定值解析后的 Promise
对象。如果这个值是一个 promise ,那么将返回这个 promise ;如果这个值是 thenable(即带有 "then"
方法),返回的 promise 会“跟随”这个 thenable 的对象,采用它的最终状态;否则返回的 promise 将以此值完成。此函数将类 promise 对象的多层嵌套展平。[MDN 文档](Promise.resolve() - JavaScript | MDN (mozilla.org))
// 缓存数据
function query(name) {
const cache = queery.cache || (query.cache = new Map())
if(cache.has(name)) {
return Promise.reolve(cache.get(name))
}
return ajax(`xxx?name=${name}`)
.then(res => {
cache.set(name, res)
return res
})
}
query('john').then(user => {
console.log(user)
})
setTimeout(() => {
query('john').then(user => {
console.log(user)
})
}, 2000)
// Promise.resolve 结合 thenable
let obj = {
then(resolve, reject) {
console.log('in obj')
resolve('ok')
}
}
Promise.resolve(obj).then(v => {
console.log(v, 'over')
})
Promise.reject() 的使用
Promise.reject()
方法返回一个带有拒绝原因的 Promise
对象。
Promise.reject('fail').then(v => {
console.log(v, 'ok')
}).catch(err => {
console.log(err)
})
Promise.all() 的使用
Promise.all() 方法接收一个 promise 的 iterable 类型(注:Array,Map,Set 都属于 ES6 的 iterable 类型)的输入,并且只返回一个Promise
实例, 那个输入的所有 promise 的 resolve 回调的结果是一个数组。这个Promise
的 resolve 回调执行是在所有输入的 promise 的 resolve 回调都结束,或者输入的 iterable 里没有 promise 了的时候。它的 reject 回调执行是,只要任何一个输入的 promise 的 reject 回调执行或者输入不合法的 promise 就会立即抛出错误,并且 reject 的是第一个抛出的错误信息。 [MDN 文档 Promise.all()](Promise.all() - JavaScript | MDN (mozilla.org))
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// Array [3, 42, "foo"]
// ---------------------------- //
function getUsers(names) {
let promises = names.map(name => {
return ajax(`xxx?name=${name}`)
})
return Promise.all(promises)
}
getUsers(['john', 'jack']).then(users => {
console.log(users)
}).catch(err => {
console.log(err)
})
Promise.allSettled() (ES11/ES2020)
该 Promise.allSettled()
方法返回一个在所有给定的 promise 都已经 fulfilled
或 rejected
后的 promise,并带有一个对象数组,每个对象表示对应的 promise 结果。 当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise
的结果时,通常使用这个。 相比之下,Promise.all()
更适合彼此相互依赖或者在其中任何一个reject
时立即结束。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
Promise.allSettled(promises).then((results) =>
results.forEach((result) =>
console.log(result.status)
)
);
// "fulfilled"
// "rejected"
Promise.race()
Promise.race(iterable)
方法返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。 Promise.race() - JavaScript | MDN (mozilla.org)
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then((value) => {
console.log(value);
});
// two
使用 map() 和 reduce() 实现 Promise 队列
function queue(num) {
let promise = Promise.resolve()
num.map(v => {
promise = promise.then(_ => {
return new Promise(resolve => {
setTimeout(() => {
console.log(v)
resolve()
}, 1000)
})
})
})
}
queue([1, 2, 3]) // 每隔一秒打印出 1 2 3
// 另外一个写法
function queue(num) {
let promise = Promise.resolve()
num.map(v => {
promise = promise.then(_ => {
return v()
})
})
}
function p1() {
return new Promise(resolve => {
setTimeout(() => {
console.log('p1')
resolve()
}, 1000)
})
}
function p2() {
return new Promise(resolve => {
setTimeout(() => {
console.log('p2')
resolve()
}, 1000)
})
}
queue([p1, p2]) // 依次打印出 p1 p2
// ------------------------------------------ //
// 使用 reduce 实现队列
function queue(num) {
num.reduce((promise, n) => {
return promise.then(_ => {
return new Promise(resolve => {
setTimeout(() => {
console.log(n)
resolve()
}, 1000)
})
})
}, Promise.resolve())
}
queue([1, 2, 3]) // 每隔一秒打印出 1 2 3
async 函数和关键词 await (ES8/ES2017)
返回值是一个 Promise,这个 promise 要么会通过一个由 async 函数返回的值被解决,要么会通过一个从 async 函数中抛出的(或其中没有被捕获到的)异常被拒绝。 async 函数一定会返回一个 promise 对象。如果一个 async 函数的返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中。 async
/await
的目的为了简化使用基于 promise 的 API 时所需的语法。async
/await
的行为就好像搭配使用了生成器和 promise。 async 函数 - JavaScript | MDN (mozilla.org)
await
操作符用于等待一个Promise
对象。它只能在异步函数 async function
中使用。 返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。 await - JavaScript | MDN (mozilla.org)
async function foo() {
return 'foo' // Promise.resolve('foo')
}
写一个 sleep 函数
async function sleep(delay = 1000) {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, delay)
})
}
async function show(users = []) {
for (const user of users) {
await sleep(1200)
console.log(user)
}
}
show(['hello', 'world'])
class 与 await 结合
class User {
constructor(name) {
this.name = name
}
then(resolve, reject) {
resolve({name: this.name, age: 18})
}
}
async function getName() {
let user = await new User('hello')
console.log('after new User :', user)
}
getName()
async 和 await 的错误处理
async function run() {
throw new Error('fail')
}
run().catch(err => {
console.log(err)
})
async function getUser(name) {
try {
let user = ajax(`xxx?name=${name}`)
return user
} catch(err) {
console.log(err)
}
}
getUser('xi')
.then(user => {
console.log(user)
})
.catch(err => {
console.log(err)
})
await 并行执行技巧
function p1() {
return new Promise(resolve => {
setTimeout(() => {
resolve('p1')
}, 1000)
})
}
function p2() {
return new Promise(resolve => {
setTimeout(() => {
resolve('p2')
}, 1000)
})
}
async function run() {
let res1 = p1()
let res2 = p2()
let resV1 = await res1 // 这里相当于 then 的写法
let resV2 = await res2
console.log(resV1, resV2)
// 换一个写法
let res = Promise.all([p1, p2])
console.log(res)
}