使用Javascript如何优雅的编写异步代码

在这里插入图片描述

使用Javascript如何优雅的编写异步代码

async / await 是一种与 Promise协作的特殊的语法糖。它使得我们可以像写同步代码一样书写异步代码。仅此而已

本文主要内容

  • Callback / Promise / Generator / Async / Await
  • 几个常见概念
  • 同步循环
  • 异步循环

一、Callback / Promise / Generator / Async / Await

Promise 是异步编程的一种解决方案
  • 比传统的解决方案——回调函数和事件——更合理和更强大
  • Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果
  • 从语法上说,Promise 是一个对象,从它可以获取异步操作的消息
  • Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理
1.callback传统回调方法
function printStr(str, cb) {
    setTimeout(() => {
        console.log(str)
        cb()
    }, 1000)
}

function printAll() {
    printStr('a', () => {
        printStr('b', ()=> {
	    printStr('c', () => {
	        console.log('end')
            })
	})
    })
}

printAll()
// a -> b -> c end  [after: 1000ms] [total: 3000ms]

最大的问题是:回调地狱

2.Promise 方式

首先我们封装一个 promise 异步函数

function printStr(str){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(str)
            resolve()
        }, 1000)
    })
}

然后调用then方法

then方法,可分别指定resolved状态和rejected状态的回调函数

// 此处只指定了 resolved 状态,rejected同理
// 语法:.then(()=>{/*resolved*/}, ()=>{/*rejected*/})
function printAll() {
    printStr('a')
    .then(()=> {
        return printStr('b')
    })
    .then(() => {
        return printStr('c')
    })
}

// 可以进一步简化
// 去掉箭头函数的包装
// 等价于一下函数
function printAll() {
    printStr('a')
    .then(() => printStr('b'))
    .then(() => printStr('c'))
}

printAll()
// a -> b -> c  [after: 1000ms] [total: 3000ms]
Async / await函数是ES2017(ES8)的新增功能
  • 让异步逻辑用同步写法实现
  • 最底层的await返回需要是Promise对象
  • 可以通过多层 async function 的同步写法代替传统的callback嵌套。
3.async/await 方式
async function printAll() {
    await printStr('a')
    await printStr('b')
    await printStr('c')   
}
printAll()
// a -> b -> c  [after: 1000ms] [total: 3000ms]
4.在async/await出现之前还有一种过渡方式,Generator

【已经被async/await取代】

  • 调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,遍历器对象(Iterator Object)
function*  printAll() {
    yield printStr('a')
    yield printStr('b')
}
const gen = printAll() // 调用后该函数并不执行, 返回指向内部状态的指针对象
gen.next() // [yield暂停执行 / next恢复执行]

二、几个常见概念

1.await只能在异步函数中使用

也就是 await 只能 包含在 async函数里:

async function printAll() {
    await printStr()
}
2.async 可以单独使用
async function msg() {
    return 'hello async await'
}

注意:async 函数总是返回一个promise,因此以下的结果可能不符合您预期:

const str = msg()
console.log(str)
// Promise {<resolved>: "hello async await"}

你应该这么操作:

msg().then((str)=> {
    console.log(str)
})
3.两种书写形式
// 用于测试的 promise 方法
function getData(str) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(str)
        }, 1000)
    })
}

函数表达式:

const msg1 = async function() {
    return await getData('str1')
}
msg1().then(text => console.log(text))
// str1

箭头函数表达式:

const msg2 = async() => {
    return await getData('str2')
}
msg2().then(text => console.log(text))
// str2
4.异常处理 try…catch
// 测试 promise 函数
function yayOrNay() {
    return new Promise((resolve,reject)=>{
        // 0 or 1, at random
        const val = Math.round(Math.random() * 1);
        val ? resolve('Lucky!!') : reject('Nope ?');
    });
}

将 await 包裹在 try…catch中:

async function msg() {
    try {
        const msg = await yayOrNay();
        // await xxx
        // await xxx
        // 当存在多个 await 的时候也可以同时包裹在一个try catch 里面
        console.log(msg);
    } catch (err) {
        console.log(err);
    }
}
msg()
msg()
msg()
msg()
msg()
msg()

/*
2 Lucky!!
Nope ?
Lucky!!
2 Nope ?
*/

async 函数总是返回一个promise,所以我们可以直接调用catch 进行异常处理:

async function msg() {
  const msg = await yayOrNay();
  console.log(msg);
}

msg().catch(x => console.log(x));
5.Promise几个重要API

需要明确一点:async / await是 Promise 的补充而非取代,让我们写异步代码更趋向同步。Promise还有非常强大的API,用于处理不同场景

场景:同时发送多个ajax请求

Promise.all

返回一个Promise实例

Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例
成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,失败的时候则返回最先被reject失败状态的值
Promise.all 返回的结果和传入的 promise对象顺序是一致的。例如:Promise.all( [ajax1, ajax2]),ajax1执行时间长于ajax2的时间,但返回结果依然保持顺序,result = [resAjax1, resAjax2]

function printAll() {
   // 等价于 Promise.all(['a', 'b', 'c'].map(str => printStr(str)))
   return Promise.all([printStr('a'), printStr('b'), printStr('c')])

}

printAll().then((result) => {
   console.log(result)
}).catch((error) => {
   console.log(error)
})
// a -> b -> c  [after: 1000ms] [total: 1000ms+ ]
Promise.race

返回一个最先执行完的 promise

一旦迭代器中的某个promise解决或拒绝,返回的promise就会解决或拒绝。也就是最先执行完的ajax请求

function printRace() {
    return Promise.race(['a', 'b'].map(str => printStr(str)))
}

printRace().then((result) => {
    console.log(result)
}).catch((error) => {
    console.log(error)
})
// a or b [total: 1000ms]

当然 Promise 还有 Promise.resolve()、Promise.reject()等API,但都是为了让我们在异步编程过程中更加方便


三、同步循环

同步循环是我们每天写代码过程中都会使用的,非常基础
主要是为了和异步循环做对比

function syncFun() {
    const arr = [1, 2, 3]
    for (let i = 0, len = arr.length; i < len; i++) {
        console.log(arr[i])
    }
    // or map/forEach
    /*
    arr.map((number) => {
        console.log(number)
    })
    */
}

syncFun()
// 1 2 3

四、异步循环

参考:JavaScript loops - how to handle async/await

定义一个异步函数 promise:

function asyncFun(number) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(number)
            resolve()
        }, 1000)
    })
}

在for 循环中使用 await

map、forEach 等这些高阶循环方法,循环体里面都是异步的。需要等后续操作完成,才执行循环体中的方法

所以此处输出结果不符合我们预期:

async function test() {
    console.log('start')
    const arr = [1, 2, 3]
    arr.map(async(number) => {
        await asyncFun(number)
    })
    console.log('end')
}
// start end 1 2 3
// [total: 1000ms+]
串行遍历

我们放弃使用高阶循环,使用原生循环方法,可以解决这个问题

async function test() {
    console.log('start')
    const arr = [1, 2, 3]
    for (let i = 0, len = arr.length; i < len; i++ ) {
        await asyncFun(arr[i])
    }
    console.log('end')
}

test()

// start 1 2 3 end
// [total: 3000ms]
并行遍历

如果需要让循环体里面的内容执行完了之后再执行后面的操作,我们可以用 Promise.all,实现串行输出:

async function test() {
    console.log('start')
    const arr = [1, 2, 3]
    const promises = arr.map((number) => {
        return asyncFun(number)
    })
    await Promise.all(promises)
    console.log('end')
}
// start 1 2 3 end
// [total: 1000ms +]

注意:当数据量很大的时候,会有并行性能问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值