Promise


JavaScript 执行机制


javascript是一门单线程语言不能同时处理多个任务,把任务分成了同步和异步。

事件循环Event Loop

事件循环是js实现异步的一种方法,也是js的执行机制。

  1. 同步和异步任务分别进入不同的执行”场所”,同步的进入主线程,异步的进入Event Table并注册函数。
  2. 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
  3. 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
  4. 上述过程会不断重复,也就是常说的Event Loop(事件循环)。
//举个栗子
let data = [];
$.ajax({
    url:www.javascript.com,
    data:data,
    success:() => {
        console.log('发送成功!');
    }
})
console.log('代码执行结束');
  1. ajax进入Event Table,注册回调函数success
  2. 执行console.log('代码执行结束')
  3. ajax事件完成,回调函数success进入Event Queue。
  4. 主线程从Event Queue读取回调函数success并执行

宏任务和微任务

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise,process.nextTick
setTimeout(function() {
    console.log('setTimeout');  //4
})

new Promise(function(resolve) {
    console.log('promise'); //1
}).then(function() {
    console.log('then');  //3
})

console.log('console');  //2
  1. 这段代码作为宏任务,进入主线程。
  2. 先遇到setTimeout,那么将其回调函数注册后分发到宏任务Event Queue。
  3. 接下来遇到了Promisenew Promise立即执行,then函数分发到微任务Event Queue。
  4. 遇到console.log(),立即执行。
  5. 整体代码script作为第一个宏任务执行结束,执行then在微任务Event Queue里面。
  6. 第一轮事件循环结束了,开始第二轮循环,当然要从宏任务Event Queue开始。我们发现了宏任务Event Queue中setTimeout对应的回调函数,立即执行。

promise


Promise 可以理解为承诺,就像我们去点餐服务员给我们一引订单票,这就是承诺。如果餐做好了叫我们这就是成功,如果没有办法给我们做出食物这就是拒绝。

一个 promise 必须有一个 then 方法用于处理状态改变

状态说明


Promise包含pendingfulfilledrejected三种状态

  1. pending 指初始等待状态,初始化 promise 时的状态
  2. resolve 指已经解决,将 promise 状态设置为fulfilled
  3. reject 指拒绝处理,将 promise 状态设置为rejected

promise 是生产者,通过 resolvereject 函数告之结果

promise 非常适合需要一定执行时间的异步任务

状态一旦改变将不可更改

promise 是队列状态,就像体育中的接力赛,状态一直向后传递,当然其中的任何一个promise也可以改变状态。

  • promise 没有使用 resolvereject 更改状态时,状态为 pending

        console.log(
            new Promise((resolve, reject) => {})
        )//Promise{<pending>}
  • promise状态更改为resolve或reject之后

    console.log(
                new Promise((resolve, reject) => {
                    resolve('felfilled')
                })
            ) //Promise{<felfilled>:felfilled}
    
     console.log(
            new Promise((resolve, reject) => {
                reject('rejected')
            })
        ); //Promise{<rejected>:rejected}
  • promise 创建时即立即执行即同步任务,then 会放在异步微任务中执行,需要等同步任务执行后才执行。

  • promise 的 then、catch、finally的方法都是异步任务

  • 程序需要将主任务执行完成才会执行异步队列任务

        let promise = new Promise((resolve, reject) => {
            resolve('iamys.club');
            console.log('世界');
        }).then(msg => {
            console.log(msg);
        })
        console.log('你好');
        //世界
        //你好
        //iamys.club
    • Promise里的是微任务比回调函数的宏任务先执行
       new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('定时器');
            }, 3000);
            resolve("fulfilled");
        }).then(
            msg => {
                console.log(msg);
            },
            error => {
                console.log(error);
            }
        );
        //felfilled
        //定时器
  • 状态被改变后就不能再修改了。下面先通过resolve 改变为成功状态,表示promise 状态已经完成,就不能使用 reject 更改状态了

        new Promise((resolve, reject) => {
            resolve("操作成功");
            reject(new Error("请求失败"));
        }).then(
            msg => {
                console.log(msg);
            },
            error => {
                console.log(error);
            }
        );
        //操作成功

动态改变


  • 如果 resolve 参数是一个 promise ,将会改变promise状态。

    下例中 p1 的状态将被改变为 p2 的状态

        const p1 = new Promise((resolve, reject) => {
            resolve(
                //p2
                new Promise((s, e) => {
                    e("失败");
                })
            );
        }).then(msg => {
                console.log(msg);
            },
            reson => {
                console.log(reson);//失败
            }
        );
  • 当promise做为参数传递时,需要等待promise执行完才可以继承

    下面的p2需要等待p1执行完成。因为p2resolve 返回了 p1 的promise,所以此时p2then 方法已经是p1 的了,所以 then 的第一个函数输出了 p1resolve 的参数

     const p1 = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve("操作成功");
            }, 2000);
        });
        const p2 = new Promise((resolve, reject) => {
            resolve(p1);
        }).then(
            msg => {
                console.log(msg);//操作成功
            },
            error => {
                console.log(error);
            }
        );

then


一个promise 需要提供一个then方法访问promise 结果,then 用于定义当 promise 状态发生改变时的处理,即promise处理异步操作,then 用于结果。

  • then 方法必须返回 promise,用户返回或系统自动返回
  • 第一个函数在resolved 状态时执行,即执行resolve时执行then第一个函数处理成功状态,如果只关心失败时状态,then 的第一个参数传递 null
  • 第二个函数在rejected状态时执行,即执行reject 时执行第二个函数处理失败状态,该函数是可选的
  • 两个函数都接收 promise 传出的值做为参数
  • 也可以使用catch 来处理失败的状态,那就是对所有没有指定失败返回的统一的失败返回
  • 如果 then 返回 promise ,下一个then 会在当前promise 状态改变后执行

链式调用

promise 中的 then 方法可以链接执行,then 方法的返回值会传递到下一个then 方法。

  • then 会返回一个promise ,所以如果有多个then 时会连续执行

  • then 返回的值会做为当前promise 的结果

  • 第一个then 是对上个promise 的状态的处理,每个 then 会是一个新的promise,默认传递 fulfilled状态,上一个 promise状态不会影响以后then返回的状态

        new Promise((resolve, reject) => {
                reject();
            })
            .then(
                resolve => console.log("fulfilled"),
                reject => console.log("rejected")
            )
            .then(
                resolve => console.log("fulfilled"),
                reject => console.log("rejected")
            )
            .then(
                resolve => console.log("fulfilled"),
                reject => console.log("rejected")
            );
        // 执行结果如下
        // rejected
        // fulfilled
        // fulfilled
  • 如果内部返回promise,就用这个promise

        let p1 = new Promise(resolve => {
            resolve();
        });
        let p2 = p1.then(() => {
            return new Promise(r => {
                r("iamys.club");
            });
        });
        p2.then(v => {
            console.log(v); //iamys.club
        });
  • 如果 then 返回promise 时,后面的then 就是对返回的 promise 的处理(promise的返回值是promise也一样),其他操作需要等待该 promise 变更状态后执行。

        let promise = new Promise(resolve => resolve());
        let p1 = promise.then(() => {
            return new Promise(resolve => {
                setTimeout(() => {
                    console.log(`p1`);
                    resolve();
                }, 2000);
            });
        }).then(() => {
            return new Promise((a, b) => {
                console.log(`p2`);
            });
        });
        //p1
        //p2
  • 如果then返回 promise 时,返回的promise 后面的then 就是处理这个promise

    new Promise((resolve, reject) => {
      resolve();
    })
    .then(v => {
      return new Promise((resolve, reject) => {
        resolve("第二个promise");
      });
    })
    .then(value => {
      console.log(value); //第二个promise
      return value;
    })
    .then(value => {
      console.log(value); //第二个promise
    });

其它类型

  • 如果 then 返回与 promise 相同将禁止执行

    let promise = new Promise(resolve => {
      resolve();
    });
    let p2 = promise.then(() => {
      return p2;
    }); // TypeError: Chaining cycle detected for promise
  • 包含 then 方法的对象就是一个 promise ,享受和promise一样的特性和待遇,如果对象中的 then 不是函数,则将对象做为值传递

    new Promise((resolve, reject) => {
            resolve(
                class {
                    static then(resolve, reject) {
                        setTimeout(() => {
                            resolve("解决状态");
                        }, 2000);
                    }
                }
            );
        }).then(
            v => {
                console.log(`felfilled: ${v}`);//felfilled:解决状态
            },
            v => {
                console.log(`rejected: ${v}`);
            }
        );

catch


catch用于失败状态的处理函数,等同于 then(null,reject){}

  • 建议使用 catch 处理错误

  • catch 放在最后面用于统一处理前面发生的错误

  • 错误是冒泡的操作的,只要一个then 没有定义第二个函数,将一直冒泡到 catch 处理错误

  • catch 也可以捕获对 then 抛出的错误处理

       new Promise((resolve, reject) => {
                resolve();
            })
            .then(msg => {
                throw new Error("这是then 抛出的错误");
            })
            .catch(() => {
                console.log("33");//33
            });
  • 在异步中 throw 将不会触发 catch,而使用系统错误处理

        const promise = new Promise((resolve, reject) => {
            setTimeout(() => {
                throw new Error("fail");
            }, 2000);
        }).catch(msg => {
            console.log(msg + "123");
        });

定制错误

404错误

class ParamError extends Error {
  constructor(msg) {
    super(msg);
    this.name = "ParamError";
  }
}
class HttpError extends Error {
  constructor(msg) {
    super(msg);
    this.name = "HttpError";
  }
}
function ajax(url) {
  return new Promise((resolve, reject) => {
    if (!/^http/.test(url)) {
      throw new ParamError("请求地址格式错误");
    }
    let xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.send();
    xhr.onload = function() {
      if (this.status == 200) {
        resolve(JSON.parse(this.response));
      } else if (this.status == 404) {
        reject(new HttpError("用户不存在"));
      } else {
        reject("加载失败");
      }
    };
    xhr.onerror = function() {
      reject(this);
    };
  });
}

ajax(`url`)
.then(value => {
  console.log(value);
})
.catch(error => {
  if (error instanceof ParamError) {
    console.log(error.message);
  }
  if (error instanceof HttpError) {
    alert(error.message);
  }
  console.log(error);
});

finally


无论状态是resolvereject 都会执行此动作,finally 与状态无关

const promise = new Promise((resolve, reject) => {
  reject("hdcms");
})
.then(msg => {
  console.log("resolve");
})
.catch(msg => {
  console.log("reject");
})
.finally(() => {
  console.log("resolve/reject状态都会执行");
});

扩展接口


  1. 使用 promise.resolve 方法可以快速的返回一个promise对象
  2. Promise.resolve 类似,reject 生成一个失败的promise
  3. 使用Promise.all 方法可以同时执行多个并行异步操作,比如页面加载时同进获取课程列表与推荐课程。
    • 任何一个 Promise 执行失败就会调用 catch方法
    • 适用于一次发送多个异步操作
    • 参数必须是可迭代类型,如Array/Set
    • 成功后返回 promise 结果的有序数组
        const hdcms = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve("第一个Promise");
            }, 1000);
        });
        const houdunren = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve("第二个异步");
            }, 1000);
        });
        const hd = Promise.all([hdcms, houdunren])
            .then(results => {
                console.log(results);
            })  //["第一个Promise", "第二个异步"]
            .catch(msg => {
                console.log(msg);
            });
  1. allSettled 用于处理多个promise ,只关注执行完成,不关注是否全部执行成功,allSettled 状态只会是fulfilled
  2. 使用Promise.race() 处理容错异步,和race单词一样哪个Promise快用哪个,哪个先返回用哪个。
    • 以最快返回的promise为准
    • 如果最快返加的状态为rejected 那整个promiserejected执行cache
    • 如果参数不是promise,内部将自动转为promise
        const hdcms = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve("第一个Promise");
            }, 2000);
        });
        const houdunren = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve("第二个异步");
            }, 1000);
        });
        Promise.race([hdcms, houdunren])
            .then(results => {
                console.log(results);//第二个异步
            })
            .catch(msg => {
                console.log(msg);
            });

async/await


async/await 是promise 的语法糖,可以让编写 promise 更清晰易懂,也是推荐编写promise 的方式。

  • async/await 本质还是promise,只是更简洁的语法糖书写
  • async/await 使用更清晰的promise来替换 promise.then/catch 的方式
  1. 函数前加上async,函数将返回promise,我们就可以像使用标准Promise一样使用了。

  2. 如果有多个await 需要排队执行完成,我们可以很方便的处理多个异步队列

    使用 await 关键词后会等待promise 完

    • await 后面一般是外部的promise,如果不是直接返回
    • await 必须放在 async 定义的函数中使用
    • await 用于替代 then 使编码更优雅
      async function hd(message) {
            return new Promise(resolve => {
                setTimeout(() => {
                    resolve(message);
                }, 2000);
            });
        }
        async function run() {
            let h1 = await hd("好的");
            console.log(h1);
            let h2 = await hd("123");
            console.log(h2);
        }
        run();

并发执行

有时需要多个await 同时执行,有以下几种方法处理,下面多个await 将产生等待

async function p1() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("123");
      resolve();
    }, 2000);
  });
}
async function p2() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("456");
      resolve();
    }, 2000);
  });
}
async function hd() {
  await p1();
  await p2();
}
hd();

//相当于使用 Promise.all() 处理多个promise并行执行
async function hd() {
  await Promise.all([p1(), p2()]);
}
hd();

文章作者: XiaoQi
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 XiaoQi !
 上一篇
关于ES6 关于ES6
let命令ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。 for循环的计数器,就很合适使用let命令。 let在for循环还有一个特别之处,就是设置循环变量的那部分是一个父
2020-08-04
下一篇 
类型和语法 类型和语法
1.类型 JavaScript 有 七 种 内 置 类 型:null、undefined、boolean、number、string、object 和 symbol,可以使用 typeof 运算符来查看。 变量没有类型,但它们持有的值有类
2020-08-03
  目录