阅读 58

异步发展史,这次一定会!

啥叫异步?啥叫同步?🙉

同步:

每次只有一个线程其他线程被阻塞

简单来说,就是任务一个接着一个完成。只要一个没完成其他被堵塞

异步:

不用阻塞当前线程来等待处理完成,而是允许后续操作,直至其它线程将处理完成,并回调通知此线程。

简单来说,就是任务同时进行,中途可能需要等待其他任务完成才能进行当前任务。

js为啥需要异步?🙉

js是单线程的,如果有一个任务我们要花很长时间,那岂不是这之后的代码全被阻塞了。

Js的异步编程发展史👏

其实异步编程的发展的目的是愉快地coding:用同步写法写异步

1. callback回调函数:

回调函数:异步操作执行完后触发执行的函数,类似如下格式:

$.get("http://xxx.xxxx.com/api",callback);
复制代码

优点:

  • 简单
  • 符合传统js

缺点:

  • 容易形成回调地狱 如果:我们需要请求A接口得到学生id,通过学生id请求B接口得到学生成绩,再通学生成绩请求得到排名(不是很好的例子 但大概这个意思)
fetch({
    url: "/student",
    data:1,
    success: function (id) {
        fetch({
            url: "/score",
            data:id,
            success: function (score) {
                fetch({
                    url: "/rank",
                    data:score,
                    success: function (rank) {
                        console.log(rank)
                    }
                })
            }
        })
    }
})
复制代码
  • 回调嵌套会导致代码难以维护
  • 并且不方便统一处理错误(不能 try catch)

2. Promise:

Promise 一定程度上解决了回调地狱的问题,Promise 最早由社区提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。Promise对象简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

fetch(1).then(id=> {
    return fetch(id);
}).then(score => {
    return fetch(score);
}).then(rank=> {
    console.log(rank)
}).catch(reason => {
    console.log(reason);
});
复制代码

优点:

  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果
  • 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数

缺点:

  • 无法取消 Promise
  • 当处于pending状态时,无法得知目前进展到哪一个阶段
  • 错误不能被 try catch

3. Generator

Generator 函数是 ES6 提供的一种异步编程解决方案,整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield 语句注明。 将函数分割出好多个部分,调用一次next就会继续向下执行。返回结果是一个迭代器,迭代器有一个next方法。

function my_co (it) {
    return new Promise((resolve, reject) => {
        function next(data) {
            let {value, done} = it.next(data);
            if(!done) {
                value.then(val => {
                    next(val);
                }, reject);
            }else{
                resolve(value);
            }
            
        }
        next();
    });
}

function *getData() {
    let res=yield fetch('/student',()=>{});
    let res1=yield fetch('/score',res1,()=>{});
    let res2=yield fetch('/rank',res2,()=>{});
    return res2
}
let it=getData()
let id=it.next(it)
let score=it.next(id)
let rank=it.next(score)
my_co(getData()).then(data => {
    console.log(data);
});
复制代码

特别注意:
看上去似乎我们日常业务中generator的异步写法不常用,但实际上在某些库的开发中它有着重要用途。
对!说的就是那个redux-saga
而且仔细想想还有那个异步编程的方式有这么大的控制性,想停就停!

4. Aync await

async-await其实是一个语法糖,它的实现就是将 Generator函数和自动执行器(co),包装在一个函数中。
async相当于用Promise.resolve()包裹其函数返回值。
await相当于generatorpromise的语法糖,相当于yield,但他之外的同步代码可以自动执行
🔔如果没有依赖性的话也可以用promise.all

//如上面那个例子
function fetchData(api) {
      let result
      let url = 'http://localhost:3000'
      fetch(url + api, {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
        },
      })
        .then((res) => {
          return res.json()
        })
        .then((json) => {
          console.log(json)
          result=json
        })
       return result
}
async function getData(){
    let id=await fetchData('/student')
    let score=await fetchData('/score')
    let rank=await fetchData('/rank')
}
复制代码

优点:

  • 代码清晰,
  • 不用像 Promise 写很多 then ,
  • 可以处理回调地狱的问题
  • 并且错误可以被try catch
async function main() {
  try {
    const val1 = await firstStep();
    const val2 = await secondStep(val1);
    const val3 = await thirdStep(val1, val2);

    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err);
  }
}
复制代码

🌰试一试:

git 地址
内含关于四种方式异步编程的例子,欢迎大家试一试👀

附加

  1. js是怎么实现异步执行的?
    执行栈:
    执行栈可以看作是一个存储函数调用的栈结构,遵循先进后出。 执行js时就会往执行栈放入函数。遇到异步则被挂起放入任务队列。
    EventLoop:
    等执行栈空了,eventloop就会把要执行的代码从队列中放入执行栈。\
  2. WebWorker是真的多线程吗?详见【WebWorker,这次一定会!】