小帅の技术博客 小帅の技术博客
首页
  • 基础
  • 框架
  • 进阶
  • 工程化
  • NodeJS
  • 脚本
  • 拓展
  • 技术
  • 服务器
程序猿
关于
友链
  • 分类
  • 标签
  • 归档
GitHub

前端小帅

学而不思则罔,思而不学则殆
首页
  • 基础
  • 框架
  • 进阶
  • 工程化
  • NodeJS
  • 脚本
  • 拓展
  • 技术
  • 服务器
程序猿
关于
友链
  • 分类
  • 标签
  • 归档
GitHub
  • JavaScript

    • 类型检测
    • 加减乘除
    • Promise&Generator&Async
      • Promise
        • 概述
        • Promise States
        • Promise.prototype.then
        • Promise.prototype.catch
        • Promise.prototype.finally
        • Promise.resolve
        • Promise.reject
        • Promise.all
        • Promise.race
      • Generator
      • Async
      • 错误捕获
        • promise 的异常捕获
        • async/await 的异常捕获
  • 面试

  • 前端[废弃]
  • JavaScript
sunss
2020-10-28

Promise&Generator&Async 异步函数剖析

提示

对于执行过程,只要理解了JS单线程的EvenLoop事件轮询机制便可迎刃而解。

# Promise

心谭博客

史上最易读懂的 Promise/A+ 完全实现

带你写出符合Promise/A+规范Promise的源码

# 概述

  • promise 是一个有then方法的对象或者是函数

# Promise States

  1. Promise 必须处于以下三个状态之一: pending, fulfilled 或者是 rejected

    • 如果promise在pending状态:可以变成 fulfilled 或者是 rejected
    • 如果promise在fulfilled状态:不会变成其它状态(有成功的value值)
    • 如果promise在rejected状态:不会变成其它状态(有失败的reason)

# Promise.prototype.then

  1. then之后会返回一个promise,由内部完成实现

  2. promise的 then 方法接收两个参数 promise.then(onFulfilled, onRejected)

    • onFulfilled 和 onRejected 都是可选参数,必须是函数类型,只能被调用一次
    • 状态变更后所有的onFulfilled 和 onRejected都会被调用(多个promise.then(onFulfilled,onRejected))

# Promise.prototype.catch

  1. 用于指定出错时的回调,是特殊的then方法,catch之后,可以继续 .then,效果同then第二个参数

    • 实现原理
    Promise.prototype.catch = function (onRejected) {
      return this.then(null, onRejected);
    }
    
    1
    2
    3

# Promise.prototype.finally

  1. 不管成功还是失败,都会走到finally中,并且finally之后,还可以继续then。并且会将值原封不动的传递给后面的then

    • 实现原理
    Promise.prototype.finally = function (callback) {
        return this.then((value) => {
            return Promise.resolve(callback()).then(() => {
                return value;
            });
        }, (err) => {
            return Promise.resolve(callback()).then(() => {
                throw err;
            });
        });
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

# Promise.resolve

  1. 如果 value 是个 thenable 对象,返回的promise会“跟随”这个thenable的对象,采用它的最终状态

  2. 如果传入的value本身就是promise对象,那么Promise.resolve将不做任何修改、原封不动地返回这个promise对象。

  3. 其他情况,直接返回以该值为成功状态的promise对象。

    • 实现原理
    Promise.resolve = function (param) {
        if (param instanceof Promise) {
            return param;
        }
        return new Promise((resolve, reject) => {
            if (param && typeof param === 'object' && typeof param.then === 'function') {
                setTimeout(() => { // setTimeout 为模拟异步操作,内部实现原理并非如此
                    param.then(resolve, reject);
                });
            } else {
                resolve(param);
            }
        });
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

# Promise.reject

  1. Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。

    • 实现原理
    Promise.reject = function (reason) {
        return new Promise((resolve, reject) => {
            reject(reason);
        });
    }
    
    1
    2
    3
    4
    5

# Promise.all

  1. 参数接收一个数组/可迭代对象,Promise.all(promises) 返回一个promise对象

  2. 如果传入的参数是一个空的可迭代对象,那么此promise对象回调完成(resolve),只有此情况,是同步执行的,其它都是异步返回的。

  3. 如果传入的参数不包含任何 promise,则返回一个异步完成.

  4. 所有promise都满足成功状态才会执行resolve,否则执行reject

  5. 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组

    • 实现原理
    Promise.all = function (promises) {
        promises = Array.from(promises);//将可迭代对象转换为数组
        return new Promise((resolve, reject) => {
            let index = 0;
            let result = [];
            if (promises.length === 0) {
                resolve(result);
            } else {
                function processValue(i, data) {
                    result[i] = data;
                    if (++index === promises.length) {
                        resolve(result);
                    }
                }
                for (let i = 0; i < promises.length; i++) {
                      //promises[i] 可能是普通值
                      Promise.resolve(promises[i]).then((data) => {
                        processValue(i, data);
                    }, (err) => {
                        reject(err);
                        return;
                    });
                }
            }
        });
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

# Promise.race

  1. 接收参数同all方法

  2. Promise.race函数返回一个 Promise

  3. 它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。

    • 实现原理
    Promise.race = function (promises) {
        promises = Array.from(promises);//将可迭代对象转换为数组
        return new Promise((resolve, reject) => {
            if (promises.length === 0) {
                return;
            } else {
                for (let i = 0; i < promises.length; i++) {
                    Promise.resolve(promises[i]).then((data) => {
                        resolve(data);
                        return;
                    }, (err) => {
                        reject(err);
                        return;
                    });
                }
            }
        });
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

# Generator

# Async

  • 可以理解为Generator的语法糖,用法更简单,语义更明了

    • 内置执行器:直接调用函数即可返回最终值,Generator需要手动调用 next() 继续往下执行。
    • 更好的语义:async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
    • 返回值是Promise:Generator通过yield对任务进行切分返回的是Iterator。
    • 更广的适用性:async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。

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

  • 看下面代码和注释分析

// 正常来说await后面放一个Promise,不过其实后面可以跟任意JavaScript的值,
// 如果不是promise,会被制转为promise,所以await 20效果如下
async function foo() {
  const v = await 20;
  return v;
}
const p = foo(); //=> Promise
p.then(console.log); // 20

// V8对于await的处理,执行foo()时,
// 它把参数v封装成一个promise,然后会暂停直到promise完成,
// 然后w赋值为已完成的promise,最后async返回了这个值
async function foo(v) {
  const w = await v;
  return w;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • 类比promise方式
// 参考JS的EvenLoop事件轮询机制
// Promise
new Promise((resolve,reject)=>{
    console.log('立即/同步执行') //这里是同步执行的
}).then(res=>{
    console.log('异步执行') // then会放入js主线程的微任务队列中,即异步
})
// async
async function main(){
    console.log('立即/同步执行') //
    const data= await fetch() // await后的内容是放在微任务中的异步执行
    console.log('等待await执行') //会等待await执行完成后再往下执行该代码
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 错误捕获

JS 异步错误捕获二三事

  • 主要理解JS单线程EventLoop的事件轮询机制。
  • new Promise 是同步执行的,而then是微任务回调即异步。
  • 对于setTimeout这种已经排队到下一次主线程宏任务的执行错误在当前函数内是捕获不到的。不过可以通过promise进行包装通过reject接收处理。

# promise 的异常捕获

  1. 外层包裹的try catch捕获不到promise的内部错误

    • promise 内部的错误不会冒泡出来,只有通过 promise.catch() 才可以捕获
    • 代码
    // throw new Error
    function main1() {
        try {
            new Promise(() => {
                throw new Error('promise1 error')
            })
        } catch (e) {
            console.log(e.message); // 空
        }
    }
    // reject
    function main2() {
        try {
            Promise.reject('promise2 error');
        } catch (e) {
            console.log(e.message); // 空
        }
    }
    // setTimeout
    function main3() {
        try {
            setTimeout(() => {
            throw new Error('async error');
            }, 1000);
        } catch (e) {
            console.log(e, 'err');
            console.log('continue...');
        }
    }
    // 以上情况都捕获不到错误
    // 1. promise内部错误不会冒泡出来
    // 2. setTimeout已经进入到下一次的主线程,而main的栈已经退出(异步)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
  2. promise内部Error可以通过catch捕获

    • 微任务的回调,要确定是否还在同一事件循环中
    • 代码
    // reject
    new Promise((reslove, reject) => {
        reject();
    }).catch((e) => console.log('p1 error'));// p1 error
    
    // throw new Error
    new Promise((reslove, reject) => {
        throw new Error('p2 error')
    }).catch((e) => console.log('p2 error')); // p2 error
    
    // then
    // then内部错误会传递到下一个then回调,需要通过catch接收
    new Promise((reslove, reject) => {
        reslove(1)
    }).then(res=>{
        throw new Error('p2 error')
    }).catch((e) => console.log(e)); // p2 error
    
    // setTimeout
    // 主动调用reject抛出错误是可以被then/catch捕获
    new Promise((reslove, reject) => {
        setTimeout(() => {
            reject('async error');
        });
    }).catch(e => console.log(e)); // async error
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

# async/await 的异常捕获

  • 它也不会自动去 catch 错误,需要我们自己写 try catch
  • 这里可以捕获到错误,是因为生成器也有一个“throw”状态,当promise的状态reject后,会向上被“throw”执行,然后执行catch里的代码console.log('e.message', e);
const fetchError =()=> new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('err');
    });
});

async function main() {
    try {
        const res = await fetchError();
        console.log('res', res);
    } catch (e) {
        console.log('e.message', e);
    }
}
main(); // e.message err
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
编辑
#ES6
上次更新: 2024/04/15, 14:35:14
加减乘除
内容梳理

← 加减乘除 内容梳理→

最近更新
01
说说call、apply、bind是如何改变this的
03-29
02
从输入 URL 到页面加载完成发生了什么?
03-29
03
JavaScript进阶—— new 的执行过程
03-26
更多文章>
sunss | © 2020.08-2024.04 浙ICP备2022002957号-1
载入天数... 载入时分秒...  |  总访问量 次
提供CDN加速/云存储服务
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式