Promise从入门到精通

Promise从入门到精通


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


  一、为什么使用Promise?


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

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

  浏览器事件模型:



  基于浏览器的这种事件模型,所以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('出错了');
});

猜你喜欢

转载自blog.csdn.net/hanhf/article/details/80324518