前端

Promise

2017-07-31  本文已影响0人  whitsats

Promise

在JavaScript的世界中,所有代码都是单线程执行的。

由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现:

function callback() {
    console.log('Done');
}
console.log('before setTimeout()');
setTimeout(callback, 1000); // 1秒钟后调用callback函数
console.log('after setTimeout()')

异步操作会在将来的某个时间点触发一个函数调用。

AJAX就是典型的异步操作.

xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            return success(xhr.responseText);
        } else {
            return fail(xhr.status);
        }
    }
}

更好的写法可能是:

var ajax = ajaxGet('http://...');
ajax.ifSuccess(success)
    .ifFail(fail);

这种链式写法的好处在于,先统一执行AJAX逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调用success函数或fail函数。
古人云:“君子一诺千金”,这种“承诺将来会执行”的对象在JavaScript中称为Promise对象。

ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。

下面代码创造了一个Promise实例。

var promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

then方法可以接受两个回调函数作为参数,分别是成功和失败的回调函数。其中,第二个回调函数是可选参数。我们可以用catch方法接受失败的回调。

我们先看一个最简单的Promise例子:生成一个0-2之间的随机数,如果小于1,则等待一段时间后返回成功,否则返回失败:

function test(resolve, reject) {
    var timeOut = Math.random() * 2;
    console.log('set timeout to: ' + timeOut + ' seconds.');
    setTimeout(function () {
        if (timeOut < 1) {
            console.log('call resolve()...');
            resolve('200 OK');
        }
        else {
            console.log('call reject()...');
            reject('timeout in ' + timeOut + ' seconds.');
        }
    }, timeOut * 1000);
}

这个test()函数有两个参数,这两个参数都是函数,如果执行成功,我们将调用resolve('200 OK'),如果执行失败,我们将调用reject('timeout in ' + timeOut + ' seconds.')。可以看出,test()函数只关心自身的逻辑,并不关心具体的resolve和reject将如何处理结果。

有了执行函数,我们就可以用一个Promise对象来执行它,并在将来某个时刻获得成功或失败的结果:

new Promise(function (resolve, reject) {
    log('start new Promise...');
    var timeOut = Math.random() * 2;
    console.log('set timeout to: ' + timeOut + ' seconds.');
    setTimeout(function () {
        if (timeOut < 1) {
            console.log('call resolve()...');
            resolve('200 OK');
        }
        else {
            console.log('call reject()...');
            reject('timeout in ' + timeOut + ' seconds.');
        }
    }, timeOut * 1000);
}).then(function (r) {
    console.log('Done: ' + r);
}).catch(function (reason) {
    console.log('Failed: ' + reason);
});

Promise还可以做更多的事情,比如,有若干个异步任务,需要先做任务1,如果成功后再做任务2,任何任务失败则不再继续并执行错误处理函数。

要串行执行这样的异步任务,不用Promise需要写一层一层的嵌套代码。有了Promise,我们只需要简单地写:

job1.then(job2).then(job3).catch(handleError)

例如:

    function multiply(input) {
        return new Promise(function(resolve, reject) {
            console.log('calculating ' + input + ' x ' + input + '...');
            if (Math.random()*5>1) {
                setTimeout(resolve, 500, input * input);
            }else{
                reject('第二步出错')
            }
        });
    }

    // 0.5秒后返回input+input的计算结果:
    function add(input) {
        return new Promise(function(resolve, reject) {
            console.log('calculating ' + input + ' + ' + input + '...');
            if (Math.random()*5>1) {
                setTimeout(resolve, 500, input + input);
            }else{
                reject('第三步出错')
            }
        });
    }

    var p = new Promise(function(resolve, reject) {
        console.log('start new Promise...');
        if (Math.random()*5>1) {
            resolve(123);
        }else{
            reject('第一步出错')
        }
    });

    p.then(multiply)
        .then(add)
        .then(multiply)
        .then(add)
        .then(function(result) {
            console.log('Got value: ' + result);
        }).catch(function(msg){
            console.log(msg)
        });

同时,Promise还允许并行执行异步任务。

试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.race()载入多个异步,只取值最先返回的结果,后返回的结果会被直接丢弃

上一篇 下一篇

猜你喜欢

热点阅读