JavaScript异步编程

Posted mcgee0731

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript异步编程相关的知识,希望对你有一定的参考价值。

javascript异步编程

从本文你将了解到

  • 同步模式Synchronous or 异步模式Asynchronous
  • 事件循环与队列消息
  • 异步编程的几种方式
  • Promise异步方案,宏任务/微任务列队
  • Generator异步方案,Async/Await语法糖

javascript(js)是单线程从上至下执行

同步和异步模式

同步模式

按顺序排队执行,在Call stack中压入一个anonymous匿名调用,相当于把全部代码放在匿名函数中去依次执行
阻塞
同步执行时某条操作比较耗时会造成卡死

异步模式

不会等待任务结束再执行下一个任务,Callstack | WebAPIs | Event loop | Queue
代码是单线程的,浏览器不是,对于异步会产生一个异步调用线程

事件循环与队列消息

  • 代码从上至下执行,依次押入call stack主栈中
  • 遇到异步,主栈不会停止等待执行,会将异步任务压入浏览器开辟的另一个线程栈中运行
  • 两线程并行执行
  • 异步线程栈内的异步执行完毕后,会将结果从异步栈中弹出至event queue,并等待主栈执行完毕
  • 当主栈执行完毕,通过js内部的event loop事件轮询机制将监听到的已经在event queue队列中的异步结果安顺序依次压入主栈中执行
  • console控制台输出结果

异步编程的几种方式

异步编程方案的根本就是回调函数

回调函数

类似于一件事,你明确知道这件事怎么做,但你不知道这件事情所依赖的任务什么时候完成,等待任务完成后调用,回调函数拥有回调地狱问题

Promise

CommonJS社区提出了Promise规范,回调函数统一解决方案
Pending -> Fufilled -> onFufilled 异步执行成功
        -> Rejected -> onRejected 异步执行失败

Promise异步方案,宏任务/微任务列队

一个基本的promise异步

const ps = new Promise(function (resolve,reject) {
  resolve(100)

  // reject(new Error("promise rejected"))
})

ps.then(function(value){  //会等待同步代码全部执行完才会执行
  console.log("resolve",value)
},function(err) {
  console.log("reject",err)
})

console.log("first console")

封装一个ajax

//封装一个ajax
function ajax(url){
  return new Promise((res,rej)=>{
    let xhr = new XMLHttpRequest()
    xhr.open("GET",url)
    xhr.onload = function () {
      if(this.readyState==4 && this.status ==200)
      {
        res(this.responseText)
      }
      else
      {
        rej(new Error(xhr.statusText))
      }
    }
    xhr.send()
  })
}
ajax("/src/api/users.json").then(
        res=>console.log(res),
        rej=>console.log(rej)
        )

然而上面的promise并不能解决回调地狱的问题

  //回调地狱,增加复杂度
  // ajax("/src/api/users.json").then((function(urls){
  //   ajax("/src/api/users.json").then((function(urls){
  //     ajax("/src/api/users.json").then((function(urls){
    
  //     }))
  //   }))
  // }))

通过then方法链式调用

//通过promise的then方法的链式调用,使函数扁平化,避免回调嵌套
//then方法返回的也是个promise
//每一个then方法都是在为上一个then返回的promise对象去添加状态明确过后的回调
//then方法接收的函数的参数value  是上一个then方法的返回值,如果上一个方法无返回值,则当前value=null
//后面的then会等待前面的then(因为是个promise)结束后执行
ajax("/src/api/users.json")
  .then(value=>{
    console.log(111);
  }) //=>Promise
  .then(value=>{  
    console.log(222);
    return ajax("/src/api/users.json") //可以手动添加个promise返回对象
  }) //=>Promise
  .then(value=>{  
    console.log(333);
  })

promise异常处理

//Promise异常处理
//抛出一个异常,或者运算异常都会执行失败的回调
function ajaxError(){
  return new Promise((res,rej)=>{
    // foo() //添加一个不存在的方法
    // throw new Error("error") //手动抛异常
    //...
  })
}

promise异常回调写法

//异常回调的写法
//区别,写在then里的err函数只能捕获到第一个promise的异常,
//catch可以捕获到链式调用中的promise异常,因为链式调用promise异常也会随上一个then返回的promise传递
//catch更像是给promise链条注册回调
ajaxError().then(
  function () { 
  },
  function () {
  }
)
ajaxError().then(res=>{}).then(res=>{}).catch(err=>{})

promise静态方法

  • promise.resolve()
  • promise.reject()
  • promise.all()
  • promise.race()
//Promise.resolve(参数) 
//参数是变量则包裹成一个promise对象
//参数是promise对象则被原样返回
//参数是对象,并且这个对象具有和promise一样的then方法,thenable
//    应用场景是把第三方处理异步插件的then方法转化成原生的promise对象
Promise.resolve("foo") 
  .then(value=>{
    console.log(value) //"foo"
  })

new Promise((resolve,reject)=>{
  resolve("foo")
})

var promise = ajax("/src/api/users.json")
var promise2 = Promise.resolve(promise)
console.log(promise === promise2); //true

Promise.resolve({
  then(onFulfilled,onRejected){
    onFulfilled("foo")
  }
}).then(value=>{
  console.log(value) //"foo"
})
//Promise.reject()
//快速创建一个失败的对象
//Promise.all()并行执行
//接受一个数组,数组里的每个元素是个promise对象
//当数组中promise全部执行完毕,则并行异步执行结束,返回一个promise对象
//如果其中一个执行失败,那么这个promise就会以失败结束
Promise.all([
  ajax("/src/api/users.json"),
  ajax("/src/api/posts.json")
]).then(vals=>console.log(vals))

//利用promise对象处理ajax
- urls.json
{
    "urls":"/api/users.json",
    "posts":"/api/users.json"
}

- ajax.ts
ajax("/src/api/urls.json")
  .then(value=>{
    const urls = Object.values(value)
    const tasks = urls.map(url=>ajax(url))
    return Promise.all(tasks)
  })
  .then(vals=>console.log(vals))
//Promise.race()
//只要有一个promise对象执行完成,则停止执行返回一个new promise对象
const request = ajax("/src/api/users.json") 
const settime = new Promise((res,rej)=>{
  setTimeout(() => {
    rej(new Error(\'timeout\'))
  }, 800);
})

Promise.race([request,settime])
  .then(val=>console.log(val)) 
  .catch(err=>console.log(err))

//如果800ms内请求到users.json那么执行then方法
//如果超时,则执行catch方法,(可以使用network online限速)

宏任务/微任务

//执行时序,宏任务微任务
console.log("start")
setTimeout(() => {
  console.log("timeout")
}, 0)
Promise.resolve()
  .then(()=>{
    console.log(\'promise1\');
  })
  .then(()=>{
    console.log(\'promise2\');
  })
  .then(()=>{
    console.log(\'promise3\');
  })
console.log("end");

//宏任务先执行,执行结束进入回调队列的末尾,
//微任务后执行,执行结束在本轮回调队列的末尾立即执行
//执行先宏后微,弹出先微后宏
//目前绝大多数异步调用都是作为宏任务执行
//Promise对象,MutationObserver对象,node中process.nextTick微任务

Generator异步方案

//promise chain
//虽然解决了回调嵌套,但是依然无法达到同步代码的可读性
ajax(1000)
  .then(value=>{
    ajax(2000)
  })
  .then(value=>{  
    ajax(1000)
  })
  .catch(err=>{  
    console.log(err);
  })

//like sync mode 
try{
  const url = ajax("url.json")
  const url1 = ajax("url1.json")
  const url2 = ajax("url2.json")
}catch(e){
  console.error(e);   
}

如何像同步代码一样执行呢

function * foo(){
  console.log("start");
  const res = yield \'foo\' //yield的返回值是下一次next执行传入的参数
  console.log(res) 
}

const geneator = foo() //不会立即执行,生成器对象
const result = geneator.next()  //开始执行生成器中的代码
console.log(result) //start  {value:"foo",done:false}
const result1 = geneator.next("hello") //从上一次yield开始继续往下执行,next可传入参数,
console.log(result1); //hello  {value:undefined,done:true}
//done表示执行器是否执行完成,yield类似于return但是不会结束函数执行,会暂停生成器,直到再次next()调用

生成器的throw方法

function * foo1(){
  try{
    const res = yield \'asdasdasdsa\'
    // console.log(res)
  }catch(e){
    console.log(e)
  }
}

const ge = foo1()
//执行到第一个yield 返回对象.value就是此次yield执行后的返回值
console.log(ge.next("第一次传入")) //{ value: \'asdasdasdsa\', done: false }
ge.throw(new Error("error1")) //抛出异常,s会被catch捕捉并打印

使用生成器执行异步,like async mode

function * main(){
  try{
    const lastdata = yield ajaxT(1000)
    // console.log(lastdata)
    const lastdata1 = yield ajaxT(2000)
    // console.log(lastdata)
  }catch(e)
  {
    console.log(e)
  }
}

const g = main()
const result11 = g.next();
(result11.value as Promise<any>).then(data=>{
  const result22 =  g.next(data);
  if(result22.done) return
  (result22.value as Promise<any>).then(data=>{
    const result33 = g.next(data)
    if(result33.done) return
  })
})

封装生成器调用方法,递归

//递归封装一个生成器
function co(geneator){
  const g = geneator()
  function handleResult(result){
    if(result.done) return
    result.value.then(data=>{
      const nextresult = g.next(data)
      handleResult(nextresult)
    },err=>{
      g.throw(err)
    })
  }
  handleResult(g.next())
}
co(main)

以上是关于JavaScript异步编程的主要内容,如果未能解决你的问题,请参考以下文章

如何延迟或异步此 WordPress javascript 片段以最后加载以加快页面加载时间?

html 将以编程方式附加外部脚本文件的javascript代码片段,并按顺序排列。用于响应式网站,其中ma

深入理解javascript编程中的同步和异步

异步编程之Javascript Promises 规范介绍

你可能不知道的JavaScript代码片段和技巧(下)

你可能不知道的JavaScript代码片段和技巧(上)