Promise从手写到扩展 | Promise/Generator/async | [Promise系列二](二)

简介: Promise从手写到扩展 | Promise/Generator/async | [Promise系列二](二)

async/await


async/await用法


此处采用MDN的介绍和示例

介绍:


async函数是使用async关键字声明的函数。 async函数是AsyncFunction构造函数的实例, 并且其中允许使用await关键字。async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise。


用法:


function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}
async function asyncCall() {
  console.log('calling');
  const result = await resolveAfter2Seconds();
  console.log(result);
  // expected output: "resolved"
}
asyncCall();
// 输出
//"calling"
//"resolved"


ok,我们一起看一下这个示例,顺便介绍一下async/await的用法。


首先async声明的函数里面可以使用await关键字,await其后接着一个Promise,这样就完成了异步转同步的过程~


但是这就有小朋友会问了,如果await后面接的不是Promise,会发生什么?让我们尝试一下。


const asyncFn1 = async () => {
    let res = await 1;
    console.log(res,111111111111111);
}
asyncFn1()
// 输出
1 111111111111111


所以,如此来看,如果await后面接的不是Promise,就会返回其后表达式的执行结果。


ok,接下来我们对之前这个方法简单改写。


//generator
function* asyncFun() {
    let resA = yield PromiseA //promise对象
    let resB = yield PromiseB //promise对象
    let resC = yield PromiseC //promise对象
    console.log([resA, resB, resC])
}
//asyn/await
const asyncFun = async () => {
    let resA = await PromiseA //promise对象
    let resB = await PromiseB //promise对象
    let resC = await PromiseC //promise对象
    console.log([resA, resB, resC])
}


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


ok,现在我们来看一下async/awaitgenerator区别,他们的区别不仅仅写法不同这么简单~


async/await 与 generator区别


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


  • 内置执行器。Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样。
  • 更好的语义。async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
  • 更广的适用性。co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
  • 返回值是 Promise。async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。


扩展知识


此处扩展知识,是在前文中async/awaitgenerator区别处提及问题的扩展


Thunk 函数


前文说到了Thunk 函数,想必大家对这个名词并不了解,其实是我不了解,非要拉上你们,哈哈哈。


详细内容可以参考阮一峰的Thunk 函数的含义和用法,这里只会对其进行简单的介绍。


定义:它是"传名调用"的一种实现策略,用来替换某个表达式。


//传名调用的例子
function f(m){
  return m * 2;     
}
f(x + 5);
// 等同于
var thunk = function () {
  return x + 5;
};
function f(thunk){
  return thunk() * 2;
}


JavaScript 语言的 Thunk 函数:在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。


// 正常版本的readFile(多参数版本)
fs.readFile(fileName, callback);
// Thunk版本的readFile(单参数版本)
var readFileThunk = Thunk(fileName);
readFileThunk(callback);
var Thunk = function (fileName){
  return function (callback){
    return fs.readFile(fileName, callback); 
  };
};


简单总结:无论是Promise还是thunk函数,都是通过传入回调的方式来实现Generator的自动执行。这里具体的的案例可以查看Thunk 函数的含义和用法的第五章。


co模块介绍及源码


前文我们提到了co模块[此处链接是源码,大家可以自行查看~],相比大家都对它并不了解,所以在此为大家简单介绍一下~


co模块: co模块可以让你不用编写 Generator函数的执行器。Generator函数只要传入co函数,就会自动执行


用法如下:


var co = require('co');
var gen = function* () {
  var resA = yield PromiseA;
  var resB = yield PromiseB;
};
co(gen);


ok,我们了解到co模块的用法之后,我们就可以去理解一下co模块的源码了,源码部分我将只会去介绍主要部分,目的是是理解co模块开发者思路


此处先粘co方法的完整代码,大家可以先看看,我在后面进行拆分讲解~


/**
 * Execute the generator function or a generator
 * and return a promise.
 *
 * @param {Function} fn
 * @return {Promise}
 * @api public
 */
function co(gen) {
  var ctx = this;
  var args = slice.call(arguments, 1);
  // we wrap everything in a promise to avoid promise chaining,
  // which leads to memory leak errors.
  // see https://github.com/tj/co/issues/180
  return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);
    onFulfilled();
    /**
     * @param {Mixed} res
     * @return {Promise}
     * @api private
     */
    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
      return null;
    }
    /**
     * @param {Error} err
     * @return {Promise}
     * @api private
     */
    function onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }
    /**
     * Get the next value in the generator,
     * return a promise.
     *
     * @param {Object} ret
     * @return {Promise}
     * @api private
     */
    function next(ret) {
      if (ret.done) return resolve(ret.value);
      var value = toPromise.call(ctx, ret.value);
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"'));
    }
  });
}


大家一看发现其实也没有几行,所以我们摘出主要部分一起看一下~

请大家留意我的注释和代码前的描述。


首先,co函数接受Generator函数作为参数,返回一个Promise对象


function co(gen) {
  var ctx = this;
  return new Promise(function(resolve, reject) {
  });
}


在返回的Promise对象里面,co先检查参数gen是否为Generator函数。如果是,就执行该函数,得到一个内部指针对象;如果不是就返回,并将Promise对象的状态改为resolved


function co(gen) {
  var ctx = this;
  return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') gen = gen.call(ctx);
    // 注意此时得到gen已经是Generator对象,而不是Generator函数了
    if (!gen || typeof gen.next !== 'function') return resolve(gen);
  });
}


接着,执行onFulfilled函数。看我的注释吧~


function co(gen) {
  var ctx = this;
  return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') gen = gen.call(ctx);
    // 注意此时得到gen已经是Generator对象,而不是Generator函数了
    if (!gen || typeof gen.next !== 'function') return resolve(gen);
    onFulfilled();
    function onFulfilled(res) {
      var ret;
      try {
        //此时第一次执行Generator对象的next方法,赋值给ret变量(该变量相当于Generator对象的内部指针)
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }
  });
}


最后,就是关键的next函数,它会反复调用自身。


function next(ret) {
  //如果Generator对象遍历结束,就返回
  if (ret.done) return resolve(ret.value);
  //toPromise是一个内部方法,方法描述为:Convert a `yield`ed value into a promise。
  //我们就理解它是把值转换成Promise的工具方法就好
  var value = toPromise.call(ctx, ret.value);
  //使用then方法,为返回值加上回调函数,然后通过onFulfilled函数再次调用next函数,以此递归的方式可以把后续yield后面的异步操作放在前面的Promise执行结束的回调中去。
  if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
  //在参数不符合要求的情况下,将 Promise 对象的状态改为rejected,从而终止执行。
  return onRejected(
    new TypeError(
      'You may only yield a function, promise, generator, array, or object, '
      + 'but the following object was passed: "'
      + String(ret.value)
      + '"'
    )
  );
}


其实整体逻辑和咱们前文中的自动执行器也是一致的,所以仔细读一下还是很好理解的~


结束语


就这样,Promise的两篇就完整的结束了。感谢各位掘金小伙伴的阅读~


大家可以在评论区批评指正,也可以加我微信和我深入交流,如果感兴趣还阔以去阅读我之前的文章。


欢迎阅读和关注~

也可以加我的微信 hancao97 和我交流


Promise从入门到手写[Promise系列(一)]

关于整洁代码与重构的摸爬滚打

我,24岁,展望一下?

【精简版】浏览器渲染机制(完整流程概述)

浏览器的渲染机制(一)

浏览器的渲染机制(二)

相关文章
|
3月前
|
前端开发 JavaScript API
一文吃透 Promise 与 async/await,异步编程也能如此简单!建议收藏!
在前端开发中,异步编程至关重要。本文详解了同步与异步的区别,通过生活化例子帮助理解。深入讲解了 Promise 的概念、状态及链式调用,并引入 async/await 这一语法糖,使异步代码更清晰易读。还介绍了多个异步任务的组合处理方式,如 Promise.all 与 Promise.race。掌握这些内容,将大幅提升你的异步编程能力,写出更优雅、易维护的代码,助力开发与面试!
226 0
一文吃透 Promise 与 async/await,异步编程也能如此简单!建议收藏!
|
3月前
|
前端开发 JavaScript API
JavaScript异步编程:从Promise到async/await
JavaScript异步编程:从Promise到async/await
449 204
|
前端开发 JavaScript 开发者
Async 和 Await 是基于 Promise 实现
【10月更文挑战第30天】Async和Await是基于Promise实现的语法糖,它们通过简洁的语法形式,借助Promise的异步处理机制,为JavaScript开发者提供了一种更优雅、更易于理解和维护的异步编程方式。
251 1
|
9月前
|
前端开发
使用 async/await 结合 try/catch 处理 Promise.reject()抛出的错误时,有什么需要注意的地方?
使用 async/await 结合 try/catch 处理 Promise.reject()抛出的错误时,有什么需要注意的地方?
395 57
|
12月前
|
前端开发
如何使用async/await解决Promise的缺点?
总的来说,`async/await` 是对 Promise 的一种很好的补充和扩展,它为我们提供了更高效、更易读、更易维护的异步编程方式。通过合理地运用 `async/await`,我们可以更好地解决 Promise 的一些缺点,提升异步代码的质量和开发效率。
253 64
|
12月前
|
前端开发 JavaScript
async/await和Promise在性能上有什么区别?
性能优化是一个综合性的工作,除了考虑异步模式的选择外,还需要关注代码的优化、资源的合理利用等方面。
335 63
|
JSON 前端开发 JavaScript
浅谈JavaScript中的Promise、Async和Await
【10月更文挑战第30天】Promise、Async和Await是JavaScript中强大的异步编程工具,它们各自具有独特的优势和适用场景,开发者可以根据具体的项目需求和代码风格选择合适的方式来处理异步操作,从而编写出更加高效、可读和易于维护的JavaScript代码。
283 1
|
前端开发 JavaScript 开发者
JavaScript 中的异步编程:深入了解 Promise 和 async/await
【10月更文挑战第8天】JavaScript 中的异步编程:深入了解 Promise 和 async/await
|
前端开发 JavaScript UED
深入了解JavaScript异步编程:回调、Promise与async/await
【10月更文挑战第11天】深入了解JavaScript异步编程:回调、Promise与async/await
152 0