欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ECMAScript编写异步任务

程序员文章站 2022-03-04 13:23:45
...

一、基本的异步任务

JavaScript中编写异步任务使用的就是回调函数,如下面的例子:

setTimeout(() => {
  callback()
}, 1000);

这个例子是最简单的一个异步任务的例子:在1秒钟后执行回调函数

但是这种编写异步任务会有一个问题,就是容易产生回调地狱,因此提出使用下面的一些方式解决。

二、使用Promise编写异步任务以及PromiseAPI

使用Promise编写AJAX请求:

如果不使用Promise,出现多个回调任务嵌套就会产生回调地狱,Promise的出现就是为了解决回调地狱,Promise将回调放在then函数中,使异步任务的调用和异步任务的回调分开,使得代码更加清晰:

window.onload = function () {
    const getJSON = (url) => {
        return new Promise((resolve, reject) => {
            const handler = function () {
                if (this.readyState === 4) {
                    if (this.status === 200 || this.status === 304) {
                        return resolve(this.response);
                    } else {
                        return reject(new Error('未拿到数据...'));
                    }
                }
            };
            const xhr = new XMLHttpRequest();
            xhr.onreadystatechange = handler;
            xhr.open('GET', url);
            xhr.send();
        })
    };
​
    getJSON('./data.json')
        .then(data => {
        data = JSON.parse(data);
        return getJSON(data.next);
    })
        .then(data => {
        data = JSON.parse(data);
        console.log(data.hhh);
    })
        .catch(err => {
        console.log(err);
    })
        .finally(() => {
        console.log('完成!!!');
    });
}

Promise相关知识:

所谓Promise,简单的说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise对象有以下两个特点。

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、resolved(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为resolved和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

then

Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。

catch

Promise.prototype.catch方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。

如果 Promise 状态已经变成resolved,再抛出错误是无效的。

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

一般来说,不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。

跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。

finally

finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是resolved还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

all

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

上面代码中,Promise.all方法接受一个数组作为参数,p1p2p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)

p的状态由p1p2p3决定,分成两种情况。

(1)只有p1p2p3的状态都变成resolvedp的状态才会变成resolved,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

race

Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

Promise.race方法的参数与Promise.all方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。

resolve

该方法用于将现有对象转为promise对象

Promise.resolve方法的参数分成四种情况。

(1)参数是一个 Promise 实例

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

(2)参数是一个thenable对象

thenable对象指的是具有then方法的对象。Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。

(3)参数不是具有then方法的对象,或根本就不是对象

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

(4)不带有任何参数

Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。

所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve方法。

需要注意的是,立即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。 因为promise的任务是加入到微任务队列中,而setTimeout这种事件是加入在宏任务队列中

reject

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected

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

try

 

手写Promise

// 简单版
​
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
​
function MyPromise(fn) {
  // promise object
  const promiseObject = this;
  promiseObject.state = PENDING;
  // 异步任务完成时候的数据
  promiseObject.value = null;
  // 异步任务成功时的回调函数
  promiseObject.resolvedCallbacks = [];
  // 异步任务失败时的回调函数
  promiseObject.rejectedCallbacks = [];
​
  function resolve(value) {
    if (promiseObject.state === PENDING) {
      promiseObject.state = RESOLVED;
      promiseObject.value = value;
      promiseObject.resolvedCallbacks.map(cb => cb(promiseObject.value));
    }
  }
  function reject(value) {
    if (promiseObject.state === PENDING) {
      promiseObject.state = REJECTED;
      promiseObject.value = value;
      promiseObject.rejectedCallbacks.map(cb => cb(promiseObject.value));
    }
  }
  try {
    fn(resolve, reject);
  } catch (e) {
    reject(e);
  }
}
​
MyPromise.prototype.then = function(onFulfilled, onRejected) {
  const promiseObject = this;
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
  onRejected =
    typeof onRejected === 'function'
      ? onRejected
      : r => {
          throw r;
        };
  if (promiseObject.state === PENDING) {
    promiseObject.resolvedCallbacks.push(onFulfilled);
    promiseObject.rejectedCallbacks.push(onRejected);
  }
  if (promiseObject.state === RESOLVED) {
    onFulfilled(promiseObject.value);
  }
  if (promiseObject.state === REJECTED) {
    onRejected(promiseObject.value);
  }
};
​
new MyPromise(function(resolve, reject) {
  setTimeout(() => {
    console.log('拿到数据了');
    // resolve(4);
  }, 1000);
}).then(value => {
  console.log(value);
})
​
// 标准版
​
// 每一个 promise 都有一个状态
// 每一个 promise 都有一个 data 用来保存数据
// 每一个 promise 都会维护两个回调函数列表
// 每一个 promise 都会有一个 resolve 函数和一个 reject 函数
// 执行 resolve 函数时,会将当前 promise 的状态设置为完成态
// 并给当前 promise 设置值
// 并且调用所有监视该 promise 的回调函数
// 当 promise 的状态变为失败态时,调用失败回调列表中的函数
// new Promise 时就得到了一个 promise 对象
// 刚开始 promise 对象为挂起的状态
// 当该 promise 对象内部调用 resolve 的时候,就会变成成功的状态
// resolve 后会依次执行成功回调列表中的函数
​
// 可以使用 then 方法对一个 promise 对象进行监视 (注意 then 函数的 this 是被监视的 promise)
// 每个 then 函数都将返回一个新的 promise 对象,注意是新的
// 如果监视的 promise 已经成功
// 则执行 then 的成功回调函数
// then 的回调函数执行成功后,需要返回一个 promise 对象
// 这个时候可以检测 then 的回调函数有没有返回 promise 对象
// 如果返回了,则将这个函数返回的 promise 作为新的 promise 对象返回出去
// 但是这个时候 then 的回调函数不一定执行结束,因此需要使用 then 函数对返回的 promise 对象进行监视
// 这个时候 then 的回调函数会是 resolve 和 reject,即回调函数执行成功就
function MyPromise(executor) {
  var self = this
  // 当前这个 promise 对象的状态
  self.status = 'pending'
  // 当前这个 promise 的值
  // self.data = undefined
  // promise resolve 时的回调函数集
  // 因为在 promise 结束之前有可能有多个回调添加到它上面
  self.onResolvedCallback = []
  // promise reject 时的回调函数集
  // 因为在 promise 结束之前有可能有多个回调添加到它上面
  self.onRejectedCallback = []
​
  function resolve(value) {
    if (value instanceof MyPromise) {
      return value.then(resolve, reject)
    }
    setTimeout(() => {
      // 如果当前 promise 对象是挂起的状态
      if (self.status === 'pending') {
        self.status = 'resolved'
        // 设置当前 promise 的数据
        // 因为后面的任何时候都可以得到这个数据
        // 而不是像事件监听一样错过了就没有了
        self.data = value
​
        // 执行所有监听当前 promise 的回调函数
        // 告诉他们执行成功了
        for (var i = 0; i < self.onResolvedCallback.length; i++) {
          self.onResolvedCallback[i](value)
        }
      }
    })
  }
​
  function reject(reason) {
    setTimeout(() => {
      if (self.status === 'pending') {
        self.status = 'rejected'
        self.data = reason
        for (var i = 0; i < self.onRejectedCallback.length; i++) {
          self.onRejectedCallback[i](reason)
        }
      }
    })
  }
​
  // 执行 executor 并传入相应的参数
  // executor 执行的时候可能会报错
  // 因此用try..catch包裹
  try {
    executor(resolve, reject)
  } catch (e) {
    reject(e)
  }
}
​
​
// resolve 的作用是将一个 promise 对象设置为完成态
// 并执行相应的回调函数
// resolve 接收的值是 promise 的返回值
​
// then 是对一个 promise 对象的等待
// 等待 promise 完成或失败
​
// then 方法接收两个参数,onResolved onRejected
// 分别为当前 promise 成功或失败后的回调函数
MyPromise.prototype.then = function (onResolved, onRejected) {
  var self = this
  var promise2
​
  // then 的参数应该是函数,否则应该忽略
  onResolved = typeof onResolved === 'function' ? onResolved : function (value) {
    return value
  }
  onRejected = typeof onRejected === 'function' ? onRejected : function (reason) {
    throw reason
  }
​
  // 如果当前 promise 已经成功
  if (self.status === 'resolved') {
    // then 函数一定要返回一个 promise 对象
    // 无论是新的还是老的
    // 因为 promise1 的状态已经确定并且是resolved,因此调用onResolved
    // 因为考虑到有可能出错,因此使用try..catch
    return promise2 = new MyPromise(function (resolve, reject) {
      setTimeout(() => {
        try {
          var x = onResolved(self.data)
          // 如果 onResolved 的返回值是一个 promise 对象
          // 直接取他的结果作为 promise2 的结果
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          // 如果出错,以捕获到的错误作为 promise2 的结果
          reject(e)
        }
      })
    })
  }
  // 如果当前 promise 已经失败
  if (self.status === 'rejected') {
    return promise2 = new MyPromise(function (resolve, reject) {
      setTimeout(() => {
        try {
          var x = onRejected(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    })
  }
​
  // 如果当前 promise 还在执行中
  if (self.status === 'pending') {
    return promise2 = new MyPromise(function (resolve, reject) {
      self.onResolvedCallback.push(function (value) {
        try {
          var x = onResolved(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
​
      // 加入到 promise1 的回调列表中
      self.onRejectedCallback.push(function (reason) {
        try {
          var x = onRejected(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    })
  }
}
​
MyPromise.prototype.catch = function (onRejected) {
  return this.then(null, onRejected)
}
​
/**
 * resolvePromise 函数即为根据 x 的值来决定 promise2 的状态的函数
 * 也即标准中的[Promise Resolution Procedure](https://promisesaplus.com/#point-47)
 * x 为 `promise2 = promise1.then(onResolved, onRejected)` 里 `onResolved/onRejected` 的返回值
 * `resolve` 和 `reject` 实际上是 `promise2` 的 `executor` 的两个实参,因为很难挂在其它的地方,所以一并传进来。
 * 相信各位一定可以对照标准把标准转换成代码,这里就只标出代码在标准中对应的位置,只在必要的地方做一些解释
 **/
function resolvePromise(promise2, x, resolve, reject) {
  var then
  var thenCalledOrThrow = false
​
  // 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise!'))
  }
​
  // 如果 x 为 Promise, 则使 promise 接受 x 的状态
  if (x instanceof Promise) {
    // 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
    if (x.status === 'pending') {
      x.then(function (value) {
        resolvePromise(promise2, value, resolve, reject)
      }, reject)
    } else {
      // 但如果这个 Promise 的状态已经确定了,那么它肯定有一个“正常”的值
      // 而不是一个 thenable,所以这里直接取它的状态
      x.then(resolve, reject)
    }
    return
  }
​
  // 如果 x 为对象或者函数:
  if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
    try {
​
      // 2.3.3.1 因为x.then有可能是一个getter,这种情况下多次读取就有可能产生副作用
      // 即要判断它的类型,又要调用它,这就是两次读取
      then = x.then
      if (typeof then === 'function') { // 2.3.3.3
        then.call(x, function rs(y) { // 2.3.3.3.1
          if (thenCalledOrThrow) return // 2.3.3.3.3 即这三处谁选执行就以谁的结果为准
          thenCalledOrThrow = true
          return resolvePromise(promise2, y, resolve, reject) // 2.3.3.3.1
        }, function rj(r) { // 2.3.3.3.2
          if (thenCalledOrThrow) return // 2.3.3.3.3 即这三处谁选执行就以谁的结果为准
          thenCalledOrThrow = true
          return reject(r)
        })
      } else { // 2.3.3.4
        resolve(x)
      }
    } catch (e) { // 2.3.3.2
      if (thenCalledOrThrow) return // 2.3.3.3.3 即这三处谁选执行就以谁的结果为准
      thenCalledOrThrow = true
      return reject(e)
    }
  } else {
    resolve(x)
  }
}
​
new MyPromise(function (resolve, reject) {
  setTimeout(() => {
    console.log(1)
    resolve(2)
  }, 1000);
}).then(function (data) {
  console.log(data)
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      resolve(3)
    }, 1000);
  })
}).then(function (data) {
  console.log(data)
})

三、Generator的用法?怎么用Generator编写异步任务?

使用Generator编写AJAX请求:

最基础的用法:

window.onload = function() {
    const handleContent = function(data) {
        console.log('文章作者是:' + data.name)
    }
    const handleComment = function (data) {
        console.log('评论作者是:' + data.user)
    }
    /**
    * 根据 url 发送 ajax 请求
    */
    const getJSON = function(url, handler) {
        const xhr = new XMLHttpRequest()
        xhr.onreadystatechange = function () {
            if (this.readyState === 4) {
                if (this.status === 200 || this.status === 304) {
                    res = JSON.parse(this.response)
                    handler(res)
                    g.next(res, handleComment)
                }
            }
        }
        xhr.open('GET', url)
        xhr.send()
    }
​
    function* getData(url, handler) {
        let data = yield getJSON('./data/content.json', handler)
        yield getJSON(res.commentURL, handleComment)
    }
​
    const url = './data/content.json'
    let g = getData(url, handleContent)
    g.next()
}

上面这段代码.....,不用多说,就是很垃圾,耦合度很高,虽然拿到了数据,但是代码极难维护,祖传代码就是这样的。

对上面代码修改的思路就是将回调函数抽离出来单独写,就像Promise一样。

Promise一样?那我用PromisegetJSON函数封装一下不就行了?

Promise配合使用:

window.onload = function() {
​
    /**
    * 根据 url 发送 ajax 请求
    */
    const getJSON = function(url) {
        return new Promise(function(resolve, reject) {
            const xhr = new XMLHttpRequest()
            xhr.onreadystatechange = function() {
                if (this.readyState === 4) {
                    if (this.status === 200 || this.status === 304) {
                        res = JSON.parse(this.response)
                        resolve(res)
                    } else {
                        reject(new Error('ajax请求失败'))
                    }
                }
            }
​
            xhr.open('GET', url)
            xhr.send()
        })
    }
​
    function* getData() {
        let data = yield getJSON('./data/content.json')
        yield getJSON(data.commentURL)
    }
​
    const url = './data/content.json'
    let g = getData()
    g.next().value
      .then(
        data => {
            console.log('文章作者是:' + data.name)
            g.next(data).value
              .then(
                data => {
                    console.log('评论者是:' + data.user)
                }
            )
        }
    )
}

嗯,好了,现在就实现了一个基于Promise的异步请求,但是......怎么又但是,但是这个then一层一层的,和回调地狱没什么差别呀...

额,这....这就比较麻烦了,那这样好了,我先依次发送请求将数据请求出来,然后数据全拿到后再做处理。

基于Promise的自动执行的异步任务:

window.onload = function() {
​
    const handleContent = function(data) {
        console.log('文章作者是:' + data.name)
    }
​
    const handleComment = function (data) {
        console.log('评论者是:' + data.user)
    }
    /**
    * 根据 url 发送 ajax 请求
    */
    const getJSON = function(url) {
        return new Promise(function(resolve, reject) {
            const xhr = new XMLHttpRequest()
            xhr.onreadystatechange = function() {
                if (this.readyState === 4) {
                    if (this.status === 200 || this.status === 304) {
                        res = JSON.parse(this.response)
                        resolve(res)
                    } else {
                        reject(new Error('ajax请求失败'))
                    }
                }
            }
​
            xhr.open('GET', url)
            xhr.send()
        })
    }
​
    function run(generator) {
        let g = generator()
​
        function next(data) {
            let result = g.next(data)
            if (result.done) {
                return result.value
            }
            result.value.then(function (data) {
                next(data)
            })
        }
​
        next()
    }
​
    function* getData() {
        let data = yield getJSON('./data/content.json')
        let data2 = yield getJSON(data.commentURL)
​
        handleContent(data)
        handleComment(data2)
    }
​
    run(getData)
}

完美,现在已经实现了一个异步任务,异步任务的执行,异步任务结果处理的基于GeneratorPromise的异步请求数据的代码....这名字真长...

有没有其他实现方法,不使用Promise可以写出这么完美的代码吗?答案是:可以

我们使用Promise,无非就是想使用then对异步任务进行监听,如果异步任务执行成功,就执行接下来的请求。但是,then的本质就是将监听当前Promise的任务push到一个队列里面,等异步任务执行完的时候,依次取出队列中的任务进行执行。根据这种思想,我们可以使用函数柯里化来实现对异步任务监听并将后续任务保存为一个数组:

准确来说Thunk函数并不是函数柯里化,函数柯里化实现的是函数的重新配置,是的可以单参数调用函数,而Thunk函数的主要目的是把callback分离出来

基于Thunk的自动Generator:

window.onload = function() {
​
    const handleContent = function(data) {
        console.log('文章作者是:' + data.name)
    }
​
    const handleComment = function (data) {
        console.log('评论者是:' + data.user)
    }
    /**
    * 根据 url 发送 ajax 请求
    */
    const getJSON = function(url, callback) {
        const xhr = new XMLHttpRequest()
        xhr.onreadystatechange = function() {
            if (this.readyState === 4) {
                if (this.status === 200 || this.status === 304) {
                    res = JSON.parse(this.response)
                    callback(res)
                }
            }
        }
​
        xhr.open('GET', url)
        xhr.send()
    }
​
    // ES5版本
    var Thunk = function(fn){
        return function (){
            var args = Array.prototype.slice.call(arguments);
            return function (callback){
                args.push(callback);
                return fn.apply(this, args);
            }
        };
    };
​
    // ES6版本
    const Thunk2 = function(fn) {
        return function (...args) {
            return function (callback) {
                return fn.call(this, ...args, callback);
            }
        };
    };
​
​
    function run(generator) {
        let g = generator()
​
        function next(data) {
            let result = g.next(data)
            if (result.done) {
                return result.value
            }
            result.value(next)
        }
​
        next()
    }
​
    let getJSONThunk = Thunk(getJSON)
​
    function* getData() {
        let data = yield getJSONThunk('./data/content.json')
        let data2 = yield getJSONThunk(data.commentURL)
​
        handleContent(data)
        handleComment(data2)
    }
​
    run(getData)
}

有没有封装好的,可以让Generator自动执行,当然有!!!一提到封装好的就来劲,终于不用自己写了。

Thunkify模块:

Thunkify模块的主要任务是将一个带有回调的函数转为Thunk函数,但是run函数还是要自己写,下面是Thunkify的源码:

function thunkify(fn) {
  return function() {
    var args = new Array(arguments.length);
    var ctx = this;
​
    for (var i = 0; i < args.length; ++i) {
      args[i] = arguments[i];
    }
​
    return function (done) {
      var called;
​
      args.push(function () {
        if (called) return;
        called = true;
        done.apply(null, arguments);
      });
​
      try {
        fn.apply(ctx, args);
      } catch (err) {
        done(err);
      }
    }
  }
};

co模块:

co模块是基于Promise的,不需要使用Thunk函数,另外co函数内部帮你实现了run函数,因此直接用就可以了,省时省力,还是别人的*好用....

function* getData() {
    let data = yield getJSONThunk('./data/content.json')
    let data2 = yield getJSONThunk(data.commentURL)
​
    handleContent(data)
    handleComment(data2)
}
​
co(getData)

Generator相关知识:

yield表达式

Iterator接口的关系:

next方法

for...of循环

throw方法

return方法

yield*

this

四、async函数的用法?怎么用async函数编写异步任务?

使用async/await编写AJAX请求:

还记的前面累死累活的用Generator写异步任务吗?现在有了async函数,就简单的多了,async函数是Generator的语法糖,可以简化我们的代码:

window.onload = function() {
​
    const handleContent = function(data) {
        console.log('文章作者是:' + data.name)
    }
​
    const handleComment = function (data) {
        console.log('评论者是:' + data.user)
    }
    /**
    * 根据 url 发送 ajax 请求
    */
    const getJSON = function(url) {
        return new Promise(function (resolve, reject) {
            const xhr = new XMLHttpRequest()
            xhr.onreadystatechange = function() {
                if (this.readyState === 4) {
                    if (this.status === 200 || this.status === 304) {
                        res = JSON.parse(this.response)
                        resolve(res)
                    }
                }
            }
​
            xhr.open('GET', url)
            xhr.send()
        })
    }       
​
    const asyncGetJSON = async function () {        
        let data = await getJSON('./data/content.json')
        let data2 = await getJSON(data.commentURL)
        handleContent(data)
        handleComment(data2)
    }
    asyncGetJSON()
}

一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

async函数对Generator 函数的改进,体现在以下四点。

(1)内置执行器。

Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

(2)更好的语义。

asyncawait,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

(3)更广的适用性。

co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是Promise对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolvedPromise 对象)。

(4)返回值是Promise

async函数的返回值是Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

进一步说,async函数完全可以看作多个异步操作,包装成的一个Promise 对象,而await命令就是内部then命令的语法糖。

 

相关标签: ECMAScript