note
  • Introduction
  • algorithm
    • Array
    • BinarySearch
    • Bits
    • Design
    • DFS
    • DP
    • DP_bag
    • DP_stock.md
    • Hash
    • Heap
    • NodeList
    • Number
    • SlideWindow
    • Sort
    • Stack
    • String
    • Tree
  • Backend
    • express
    • koa2
    • node
    • npm
    • npx
  • db
    • mongoDB
  • Frontend
    • CSS
      • BFC
      • flex
      • layout
      • less
      • middle
      • position
      • sass
      • selector
      • 动画相关属性
      • 响应式页面
      • 层叠上下文
      • 隐藏元素
    • JS
      • Array
      • async
      • DOM
      • ES6
      • JS-军规
      • macrotask-microtask
      • practice
      • RegExp
      • this-prototype-inherit
      • type-convert
      • 前端请求
      • 浏览器加载
      • 浏览器窗口间通信
    • TS
      • note
    • Vue
      • practice
      • Component-Communicate
      • Component
      • lifecycle
      • note
      • Pinia
      • practice
      • Vue-cli
      • Vue-Router
      • Vue-Source
      • Vue2
      • Vue3
      • Vuex
    • cross-domain
    • webpack
    • 从前端角度理解缓存
    • 前端概念
    • 页面性能分析与优化
    • 页面渲染机制
  • Linux
    • basic-command
    • docker
    • java-env
    • learn
    • manage-command
    • nginx
    • Shell
    • ssh
    • tmux
    • vi
  • chrome-devtools
  • git
  • Jenkins
Powered by GitBook
On this page
  1. Frontend
  2. JS

async

PreviousArrayNextDOM

Last updated 2 years ago

why async

传统callback方式

asyncCallone(() => {
  asyncCallTwo(() => {
    asyncCallThree(() => {
      ...
    })
  })
})

Promise

  • 解决问题

    • 多层嵌套的问题

    • 每种任务的处理结果存在两种可能性(fulfilled or rejected, default is pending),那么需要在每种任务执行结束后分别处理这两种可能性

  • 如何解决

    • 回调函数延迟绑定

    • 返回值穿透

    • 错误冒泡

  • 缺点

    1. 无法取消Promise,一旦新建它就会立即执行,无法中途取消

    2. 如果不设置回调函数,promise内部抛出的错误,不会反应到外部

    3. 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

  • API

    • Promise.resolve

      • 若参数是Promise实例,那么Promise.resolve将不做任何修改,原封不动地返回这个实例

      • 若参数是thenable对象,Promise.resolve方法会将这个对象转为Promise对象,然后就立即执行thenable对象的then方法

      • 若参数是一个原始值,或是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved

      • 若不带有任何参数,直接返回一个resolved状态的 Promise 对象

    • Promise.reject(其参数会原封不动地作为reject的理由,变成后续方法的参数,这一点与Promise.resolve方法不一致)

    • Promise.all(其中任意一个 promise 被 reject ,Promise.all 就会立即被 reject ,数组中其它未执行完的 promise 依然在执行, Promise.all 没有任何措施可以取消它们的执行)

      • 适合场景:彼此相互依赖,其中任何一个被 reject ,其它都失去了价值

    • Promise.allSettled

      • 适合场景:彼此互不依赖,其中任何一个被 reject ,对其它都没有影响

      • 适合场景:期望知道每个 promise 的执行结果

    • Promise.any

    • Promise.prototype.then

    • Promise.prototype.finally

      • 由于无法知道promise的最终状态,所以finally的回调函数中不接收任何参数,仅用于无论最终结果如何都要执行的情况

      • 与Promise.resolve(2).then(() => {}, () => {}) (resolved的结果为undefined)不同,Promise.resolve(2).finally(() => {}) resolved的结果为2,同样的Promise.reject(3).finally(() => {})rejected 的结果为3

  • Knowledge Point

    • 三种状态:pending、fulfilled和rejected

    • 初始状态是pending, 执行了resolve,Promise状态会变成fulfilled; 执行了reject,Promise状态会变成rejected

    • Promise状态不受外界影响

    • Promise只以第一次决议为准,第一次成功就永久为fulfilled,第一次失败就永远状态为rejected

    • Promise中有throw的话,就相当于执行了reject

    • Promise里没有执行resolve、reject以及throw的话,这个promise的状态也是pending

    • 基于上一条,pending状态下的promise不会执行then中的回调函数

    • 必须给Promise对象传入一个执行函数,否则报错

Generator

function* gen() {
    let a = yield 111;
    console.log(a);
    let b = yield 222;
    console.log(b);
    let c = yield 333;
    console.log(c);
    let d = yield 444;
    console.log(d);
}
let t = gen();
t.next(1); //第一次调用next函数时,传递的参数无效,故无打印结果
t.next(2); // 2
t.next(3); // 3
t.next(4); // 4
t.next(5); // 5

A/A

  • 语法简洁,更像是同步代码,也更符合普通的阅读习惯

  • 改进JS中异步操作串行执行的代码组织方式,减少callback的嵌套

  • Promise中不能自定义使用try/catch进行错误捕获,但是在Async/await中可以像处理同步代码处理错误

async返回值

const testA = async () => 1

testA().then(() => console.log(1))
Promise.resolve()
	.then(() => console.log(2))
	.then(() => console.log(3))
const testB = async () => {
	return {
		then(cb) {
			cb()
		},
	}
}

testB().then(() => console.log(1))
Promise.resolve()
	.then(() => console.log(2))
	.then(() => console.log(3))
const testC = async () => {
	return new Promise((resolve, reject) => {
		resolve()
	})
}

testC().then(() => console.log(1))
Promise.resolve()
	.then(() => console.log(2))
	.then(() => console.log(3))
	.then(() => console.log(4))

await 右值类型区别

  • await后面接非 thenable 类型,会立即向微任务队列添加一个微任务then,但不需等待

非 thenable

async function test() {
	console.log(1)
	await 1
	console.log(2)
}

test()
console.log(3)
function func() {
	console.log(2)
}

async function test() {
	console.log(1)
	await func()
	console.log(3)
}

test()
console.log(4)
async function test() {
	console.log(1)
	await 123
	console.log(2)
}

test()
console.log(3)

Promise.resolve()
	.then(() => console.log(4))
	.then(() => console.log(5))
	.then(() => console.log(6))
	.then(() => console.log(7))

thenable类型

  • await 后面接 thenable 类型,需要等待一个 then 的时间之后执行

async function test() {
	console.log(1)
	await {
		then(cb) {
			cb()
		},
	}
	console.log(2)
}

test()
console.log(3)

Promise.resolve()
	.then(() => console.log(4))
	.then(() => console.log(5))
	.then(() => console.log(6))
	.then(() => console.log(7))

==Promise类型==

async function test() {
	console.log(1)
	await new Promise((resolve, reject) => {
		resolve()
	})
	console.log(2)
}

test()
console.log(3)

Promise.resolve()
	.then(() => console.log(4))
	.then(() => console.log(5))
	.then(() => console.log(6))
	.then(() => console.log(7))
// 1 3 2 4 5 6 7
async function func() {
	console.log(1)
	await 1
	console.log(2)
	await 2
	console.log(3)
	await 3
	console.log(4)
}

async function test() {
	console.log(5)
	await func()
	console.log(6)
}

test()
console.log(7)

Promise.resolve()
	.then(() => console.log(8))
	.then(() => console.log(9))
	.then(() => console.log(10))
	.then(() => console.log(11))
//5 1 7 2 8 3 9 4 10 6 11

await一定要等到右侧的表达式有确切的值才会放行,否则将一直等待

function func() {
	return new Promise((resolve) => {
		console.log('B')
		// resolve()
	})
}

async function test() {
	console.log(1)
	await func()
	console.log(3)
}

test()
console.log(4)
// 1 B 4 (永远不会打印3)

//or
async function test() {
	console.log(1)
	await new Promise((resolve) => {
		console.log('B')
		// resolve()
	})
	console.log(3)
}

test()
console.log(4)
// 1 B 4 (永远不会打印3)

demo

async getBookByAuthor(authorId) {
  const books = await bookModel.fetchAll()
  return books.filter(b => b.authorId === authorId)
}
//promise way
getBooksByAuthor2(authorId) {
  return bookModel.fetchAll().then(books => books.filter(b => b.authorId === authorId))
}
let p1 = Promise.reject(100)
async function fn1() {
  let result = await p1
  console.log(1) //dead code
}

Practices

并行调用

//wrong
async getBooksByAuthor(authorId) {
  const books = await bookModel.fetchAll()
  const author = await authorModel.fetch(author)
  return {
    author,
    books: books.filter(book => book.authorId === authorId)
  }
}

//correct
async getBooksByAuthor(authorId) {
  const bookPromise = bookModel.fetchAll()
  const authorPromise = authorModel.fetch(author)
  const book = await bookPromise
  const author = await authorPromise
  return {
    author,
    books: books.filter(book => book.authorId === authorId)
  }
}

async getAuthors(authorIds) {
  //wrong
  const authors = _.map(
    authorIds,
    id => await authorModel.fetch(id)
  )
  //correct
  const promises = _.map(authorIds, id => authorModel.fetch(id))
  const authors = await Promise.all(promises)
}

让Promise.all正常执行完成即使出现异常

function getBannerList(){
  return new Promise((resolve,reject)=>{
    setTimeout(function(){
      // 假设这里 reject 一个异常
      reject(new Error('error'))
    },300)
  })
}

function getStoreList(){
 // ...
}

function getCategoryList(){
 // ...
}

function initLoad(){
  Promise.all([
    getBannerList().catch(err=>err),
    getStoreList().catch(err=>err),
    getCategoryList().catch(err=>err)
  ]).then(res=>{
    if(res[0] instanceOf Error){
      // 处理异常
    } else {
      // 渲染数据
    }

    if(res[1] instanceOf Error){
      // 处理异常
    } else {
      // 渲染数据
    }

    if(res[2] instanceOf Error){
     // 处理异常
    } else {
      // 渲染数据
    }
  })
}

initLoad()

race使用场景

  • 使用Promise.race把异步操作和定时器放到一起,若定时器先触发,认为超时,告知用户

  • 图片等资源有多个存放路径,但是不确定哪个路径的资源更快,可以用该方法同时请求多个路径,哪个路径的资源最先拿到,使用哪个资源

function requestImg(path){
  return new Promise(function(resolve, reject){
    let img = new Image()
    img.onload = resolve
    img.onerror = reject 
    img.src = path
  })
}

// 定时功能的延迟函数
const timeout = (delay = 3000) => {
  return new Promise(function(resolve, reject){
    setTimeout(function(){
      reject('Picture request timeout')
    }, delay)
  })
}

Promise
.race([requestImg(), timeout(3000)])
.then(function(results){
  // 该资源请求在指定时间内完成
  console.log(results)
})
.catch(function(reason){
  // 该资源请求被在指定时间内没有完成
  console.log(reason)
});

allSettled使用场景

const promises = [
  fetch('/api-1'),
  fetch('/api-2'),
  fetch('/api-3'),
];
const results = await Promise.allSettled(promises)
// 过滤出成功的请求
const successfulPromises = results.filter(p => p.status === 'fulfilled');

// 过滤出失败的请求,并输出原因
const errors = results
  .filter(p => p.status === 'rejected')
  .map(p => p.reason);

removeLoadingIndicator();// 移除加载的滚动图标

红黄绿灯

const red = () => console.log('red')
const yellow = () => console.log('yellow')
const green = () => console.log('green')

const light = (cb, timeout) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			cb()
			resolve()
		}, timeout)
	})
}

let endCnt = 0
const start = () => {
	if (endCnt++ >= 3) {
		console.log('finish lighting~~')
		return
	}
	Promise.resolve()
		.then(() => {
			return light(red, 3000)
		})
		.then(() => {
			return light(yellow, 2000)
		})
		.then(() => {
			return light(green, 1000)
		})
		.then(() => {
			start()
		})
}

start()

异步执行函数

const repeat = (cb, times, delay = 1000) => {
	return async function (...args) {
		for (let i = 0; i < times; i++) {
			await new Promise((resolve, reject) => {
				setTimeout(() => {
					cb.call(null, ...args)
					resolve()
				}, delay)
			})
		}
	}
}
const repeatFn = repeat(console.log, 4, 1000)
repeatFn('hello')

generator实现

function* repeatedArr(arr) {
	let i = 0
	while (true) {
		yield arr[i++ % arr.length]
	}
}

let infiniteNameList = repeatedArr(starks)

let sleep = (ms) =>
	new Promise((resolve) => {
		setTimeout(() => {
			resolve()
		}, ms)
	})

;(async () => {
	for (const name of infiniteNameList) {
		await sleep(1000)
		console.log(name)
	}
})()

PromiseQueue

class PromiseQueue {
	constructor(tasks, concurrentCount = 1) {
		this.totals = tasks.length
		this.todo = tasks
		this.count = concurrentCount
		this.running = []
		this.complete = []
	}

	runNext() {
		return this.running.length < this.count && this.todo.length
	}

	run() {
		while (this.runNext()) {
			let promise = this.todo.shift()
			promise.then(() => {
				this.complete.push(this.running.shift())
				this.run()
			})
			this.running.push(promise)
		}
	}
}