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

Promise从入门到精通

程序员文章站 2022-05-07 23:09:40
...

Promise从入门到精通


  今天给大家详细讲讲JavaScript中的Promise,保证一听就懂。


  一、为什么使用Promise?


  JavaScript以单线程方式运行于浏览器之中,而且JavaScript和UI线程也处于同一个线程中。因此对于长时间的耗时操作,将会阻塞UI的响应。为了更好的UI体验,应该尽量的避免在JavaScript中执行耗时较长的操作或者是长时间I/O阻塞的任务。

  所以在浏览器中的大多数任务都是异步无阻塞执行的,例如:鼠标点击事件、定时器事件、Ajax回调事件等。当一个事件触发时,它会被放入浏览器的”事件队列“中。JavaScript引擎以单线程的方式一个一个地处理“事件队列”中的事件,当此次处理中再次触发异步事件,它们也会被放入事件队列中,等待下一次的处理。

  浏览器事件模型:

Promise从入门到精通


  基于浏览器的这种事件模型,所以JavaScript一直以回调的方式来处理事件。

  当碰到多个连续的JavaScript异步任务时,不可避免地会遇见”callback hell(回调地狱)“,使得这类代码难以维护。

  例如:

asyncTask1(data, function (data1) {
    asyncTask2(data1, function (data2) {
        asyncTask3(data2, function (data3) {
            // ......
        });
    });
});

  在上例中,第二个异步任务需要使用到第一个异步任务的返回结果,第三个异步任务需要使用到第二个异步任务的返回结果,导致代码层层缩进,难以阅读。

  假如使用Promise,则代码可以变成这样:

asyncTask1(data)
    .then(function (data1) {
        return asyncTask2(data1);
    }).then(function (data2) {
        return asyncTask3(data2);
    });

  可读性就大大提高了。


  二、什么是Promise?


  Promise,英语意为“承诺”。

  •   Promise对象封装了一个异步任务。
  •   当Promise对象被实例化时,立即执行异步任务。
  •   Promise对象根据“状态”的变化来决定执行哪个回调函数。


  1. Promise对象的状态


  Promise对象有三种状态:初始状态、成功状态、失败状态,即pending、fulfilled、rejected。

  Promise对象一开始处于pending状态,然后可以根据异步任务执行的成败,切换为fulfilled状态或rejected状态。


  2. Promise的构造函数


  Promise程序的代码一般是这样的:

var promise = new Promise(function (resolve, reject) {
    // 异步任务
    if ( /* 异步任务执行成功 */) {
        resolve(value);
    } else {
        reject(error);
    }
});


  由以上示例代码可以看出,Promise的构造函数需要一个参数,这个参数是一个函数,它封装了异步任务所需执行的代码。另外,这个函数还有两个参数resolve、reject,这两个函数是由JavaScript引擎提供的。


  •   resolve(value):将Promise对象的状态切换为成功状态,并返回一个指定值。value值可由用户指定。
  •   reject(error):将Promise对象的状态切换为失败状态,并返回一个指定值。error值可由用户指定。


  所以,通常在Promise构造函数的开始部分编写异步任务的代码,然后根据自己的业务逻辑,判断什么情况下异步任务是成功的,成功时调用resolve()将Promise对象的状态切换为成功状态,并触发相应事件,失败时则调用reject()。


  3. 注册Promise的回调函数


  Promise有好几个方法都可以注册回调函数,回调函数可以响应两个事件:成功、失败。

  比较常用的是then()方法。


promise.then(function (value) {

}, function (error) {

});


  then()方法可以注册两个回调函数,第一个回调函数用于响应成功事件,其参数value是由resolve(value)传过来的;第二个回调函数用于响应失败事件,其参数error是由reject(error)传过来的。第二个回调函数可以省略。


  下面我们通过一个小案例来观察Promise的执行顺序:

let promise = new Promise(function (resolve, rejeact) {
    console.log('Promise'); // 1
    resolve(); // 切换为成功状态,在事件队列中放入成功事件
});

promise.then(function () {
    console.log('Resolved'); // 3
});

for (let i = 0; i < 1000000; i++) { }
console.log('Hi'); // 2


  程序的输出结果为:

  Promise

  Hi

  Resolved


  首先,new Promise()之后,开始执行Promise内部的代码,因此首先输出“Promise”。之后的resolve()只是把Promise对象的状态设置为成功状态,这时会向事件队列放入一个成功事件,然而我们要记住,异步代码总是在同步代码之后执行的,所以代码继续向下执行,输出“Hi”。promise.then()方法注册了一个回调函数,它在Promise对象的状态切换为成功状态时被调用,因为它是异步代码,所以在最后同步代码都执行完成了,才去处理事件,执行回调函数,输出“Resolved”。


  可以使用ES6的箭头函数将上述代码改写为:

let promise = new Promise((resolve, rejeact) => {
    console.log('Promise'); // 1
    resolve(); // 切换为成功状态,在事件队列中放入成功事件
});

promise.then(() => {
    console.log('Resolved');
});

for (let i = 0; i < 1000000; i++) { }
console.log('Hi'); // 2


  4. then()方法的返回值


  由于我们希望Promise的方法可以链式调用,所以then()方法应该返回一个Promise对象。之前我们有提到过链式调用:

asyncTask1(data)
    .then(function (data1) {
        return asyncTask2(data1);
    }).then(function (data2) {
        return asyncTask3(data2);
    });


  这里是这样规定的,如果你在then()方法中直接返回一个Promise对象,则就以这个对象返回;如果返回的不是一个Promise对象,它会将返回值包装成Promise对象,即新建一个Promise对象,并将你的返回值放在value变量中。


  来看一个链式调用的小案例:

var fn = function (num) {
    return new Promise(function (resolve, reject) {
        if (typeof num === 'number') {
            resolve(num);
        } else {
            reject('TypeError');
        }
    })
}

fn(2).then(function (num) {
    console.log('first: ' + num);
    return num + 1;
}).then(function (num) {
    console.log('second: ' + num);
    return num + 1;
}).then(function (num) {
    console.log('third: ' + num);
    return num + 1;
});


  程序的输出结果为:

  first: 2

  second: 3

  third: 4


  大家能根据上面的逻辑,分析一下为什么程序的运行结果是这样的吗?


  三、Promise的其它方法


  1. catch()方法


  通常,我们会先写一长串的then()方法进行链式调用,然后在最后加上一个catch()方法,以捕获Promise的错误。catch()方法的作用是注册失败状态的回调函数。

  在链式调用中,只要有一个then()方法导致状态失败,后续的Promise就不会再执行,转而执行catch()方法中的回调函数。


  2. all()方法


  当我们需要等待多个异步任务处理完成后,再继续向下执行,可以将这几个异步任务归为一组。

  Promise.all()方法接收一个Promise对象组成的数组作为参数,当这个数组所有的Promise对象的状态都变成fulfilled或者rejected的时候,它才会去调用then()方法。

  3. race()方法


  与Promise.all()方法相似的是,Promise.race()方法也接收一个Promise对象组成的数组作为参数,不同的是,只要数组中的任意一个Promsie对象的状态变成fulfilled或者rejected时,就会调用then()方法。


  四、Promise的应用


  下面,我们使用Promise来封装Ajax调用。我们编写了一个函数,叫getJSON(url),它可以访问指定url,并返回json格式的数据。

  代码如下:

var getJSON = function (url) {
    var promise = new Promise((resolve, reject) => {
        var client = new XMLHttpRequest();
        client.open('GET', url);
        client.onreadystatechange = handler;
        client.responseType = 'json';
        client.setRequestHeader('Accept', 'application/json');
        client.send();

        function handler() {
            if (this.readyState !== 4) {
                return;
            }
            if (this.status === 200) {
                resolve(this.response);
            } else {
                reject(new Error(this.statusText));
            }
        }
    });

    return promise;
};

getJSON('/posts.json').then(json => {
    consoloe.log(json);
}, error => {
    console.log('出错了');
});

相关标签: promise