home > webfront > ECMAS > js >

Javascript异步回调细数:promise yield async/await

author:zhoulujun@live.cn    hits:

对于Javascript处理异步,从最早的回调函数,到 Promise 对象,再到 Generator 函数,每次都有所改进,但又让人觉得不彻底。它们都有额外的复杂性,都需要理解抽象的底层运行机制。即使到es7的Async await,奈何依然然并卵。

虽然我对js的鄙视一直都是无以复加,但是奈何前端环境不得不依赖javascript。哪些nodejs的大神们四处布道nodejs统治一切:单线程非阻塞,高IO操作。但是,java也可以做好吧,而且GO做的更干练!假设你的应用程序要做两件事情,分别是A和B。你发起请求A,等待响应,出错。发起请求B,等待响应,出错。Go语言的阻塞模型可以非常容易地处理这些异常,而换到了Node里,要处理异常就要跳到另一个函数里去,事情就会变得复杂。

Node的非阻塞模型没有了多线程,但却多出了“回调地狱”问题

所以在此谈下JS的异步回调:promise yield async/await

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。

Promise

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

function timeout(value) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      value++;
      resolve(value);
    },500)

  })
}
timeout(0).then(function (res) {
  console.log(res);
})
async function test(value) {
 let c= await timeout(value);
  console.log(c);
}
test(0)

后面有了promise,然并卵——它只是减少了嵌套,并不能完全消除嵌套;另外,采用Promise的代码看起来依然是异步的。

推荐阅读《Promise简单实现(正常思路版)》《Promises/A+规范》,这里不再赘述。

Promise特别需要注意的是,他的异常处理。

.catch()的作用是捕获Promise的错误,与then()的rejected回调作用几乎一致。但是由于Promise的抛错具有冒泡性质,能够不断传递,这样就能够在下一个catch()中统一处理这些错误。同时catch()也能够捕获then()中抛出的错误,所以建议不要使用then()的rejected回调,而是统一使用catch()来处理错误。推荐阅读:《看这一篇就够了!浅谈ES6的Promise对象

js原生手工实现promise函数

function promiseA (callback) {
  this.status = 'pending'
  let doneList = []
  let failList = []

  this.then = function (done, fail) {
    switch (this.status) {
      case 'pending':
        doneList.push(done)
        failList.push(fail)
        break
      case 'fulfilled':
        done()
        return this
      case 'rejected':
        fail && fail()
        return this
    }
  }

  function resolve (result) {
    this.status = 'fulfilled'
    setTimeout(function () {
      let value = result
      for (let i = 0; i < doneList.length; i++) {
        let temp = doneList[i](value)
        if (temp instanceof PromiseA) {
          for (i++; i < doneList.length; i++) {
            temp.then(doneList[i], failList[i])
          }
        } else {
          value = temp
        }
      }
    }, 0)
  }

  function reject (error) {
    this.status = 'rejected'
    setTimeout(function () {
      let value = error
      let temp = failList[0] && failList[0](value)
      for (let i = 1; i < failList.length; i++) {
        if (temp instanceof PromiseA) {
          temp.then(doneList[i], failList[i])
        } else {
          doneList.shift()
          failList.shift()
          value = temp
          resolve(value)
        }
      }
    }, 0)
  }
  
  callback(resolve,reject)
}

yield

其实这部分可以忽略,跳到Async/await部分了

Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

Generator 函数是一个普通函数,但是有两个特征。

  1. function关键字与函数名之间有一个星号

  2. 函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)

function* helloWorldGenerator() {
  yield 'hello';//该函数的状态
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();
console.log(hw.next());    //{ value: 'hello', done: false }  
//Generator 函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象,它的value属性就是当前yield表达式的值hello,done属性的值false,表示遍历还没有结束。
console.log(hw.next());    //{ value: 'world', done: false }
//第二次调用,Generator 函数从上次yield表达式停下的地方,一直执行到下一个yield表达式。next方法返回的对象的value属性就是当前yield表达式的值world,done属性的值false,表示遍历还没有结束。
console.log(hw.next());    //{ value: 'ending', done: true }
console.log(hw.next());    //{ value: undefined, done: true }
//第四次调用,此时 Generator 函数已经运行完毕,next方法返回对象的value属性为undefined,done属性为true。以后再调用next方法,返回的都是这个值。

for(let v of helloWorldGenerator()){
  console.log(v);    // hello world
}


yield概念,这里提出来看下:

yield 表达式 

由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志

遍历器对象的next方法的运行逻辑如下

  1. 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。

  2. 下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。

  3. 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

  4. 如果该函数没有return语句,则返回的对象的value属性值为undefined。

需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。

yield表达式与return语句既有相似之处,也有区别。

相似之处:都能返回紧跟在语句后面的那个表达式的值。

区别在于:每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield表达式。


我是不太喜欢yield这个模式的,自认为是一个狗血模式!

Async/await

async / await是ES7的重要特性之一,也是目前社区里公认的优秀异步解决方案,Async/await建立于Promise之上

  1. async用来申明里面包裹的内容可以进行同步的方式执行,await则是进行执行顺序控制,每次执行一个await,程序都会暂停等待await返回值,然后再执行之后的await。

  2. await后面调用的函数需要返回一个promise,另外这个函数是一个普通的函数即可,而不是generator。函数体内的return值,将会作为这个Promise对象resolve时的参数。

  3. await只能用在async函数之中,用在普通函数中会报错。

  4. await命令后面的 Promise 对象,运行结果可能是 rejected,所以最好把 await 命令放在 try...catch 代码块中。


推荐阅读《Javascript中的async await

Async/await之错误处理Error handling

首先推荐读下《ES6 Async/Await 完爆Promise的6个原因》,虽然这个标题就狗血,Promise也并非什么终极解决方案,还有待改善。但是其中的内容页没有必要重写。节选一部分如下:

Async/await使得处理同步+异步错误成为了现实。我们同样使用try/catch结构,但是在promises的情况下,try/catch难以处理在JSON.parse过程中的问题,原因是这个错误发生在Promise内部。想要处理这种情况下的错误,我们只能再嵌套一层try/catch,就像这样:

const makeRequest = () => {
    try {
    getJSON()
        .then(result => {
            // this parse may fail
            const data = JSON.parse(result)
            console.log(data)
        })
        // uncomment this block to handle asynchronous errors
        // .catch((err) => {
        //   console.log(err)
        // })
        } 
    catch (err) {
        console.log(err)
    }
}

但是,如果用async/await处理,一切变得简单,解析中的错误也能轻而易举的解决:

const makeRequest = async () => {
      try {
          // this parse may fail
          const data = JSON.parse(await getJSON())
          console.log(data)
      } 
      catch (err) {
          console.log(err)
      }
   }

Async/await项目项目应用

fetch

async function getData () {
    //await the response of the fetch call
    let response = await fetch('https://api.github.com/users');
    //proceed once the first promise is resolved.
    let data = await response.json();
    //proceed only when the second promise is resolved
    return data;
}
//call getData function
getData().then(data => console.log(data));//log the data

axios

class App extends React.Component{
  constructor(){
   super();
   this.state = {
    serverResponse: ''
   }
  }
  componentDidMount(){
     this.getData();
  }
  async getData(){
   const res = await axios.get('url-to-get-the-data');
   const { data } = await res;
   this.setState({serverResponse: data})
   const res = await axios.post('url-to-post-the-data', {username,password});
 }
 render(){
  return(
            {this.state.serverResponse}
       );
 }}



转载本站文章《Javascript异步回调细数:promise yield async/await》, 请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/js/2017_0118_7944.html