Promise 学习笔记

20 Mar 2016

🌺 对Promise的基本用法进行了整理,希望能够更好的掌握。

初识Promise

我觉得了解一个技术最直接的方式就是知道它是用来做什么的,可以解决什么问题。提到Promise, 离不开“回调地狱”这个问题,因为后者催生了前者。Promise的出现优雅地解决了多层嵌套回调的问题。
看下下面的小栗子感受下鲜明的对比:

// 假设一个场景,每隔一秒依次打印1、2、3
// 🌲用嵌套回调函数的写法
function log(data, cb) {
    setTimeout(function(){
        console.log(data);
        if(cb) {
            cb();
        }
    }, 1000);
}
log(1, function(){
    log(2, function(){
        log(3);
    })
})
//用这种方式,如果继续打印4、5、6...就会嵌套越来越深,
代码会变得很丑陋而且不好维护。

//🌲用Promise的写法
function log(data) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log(data);
            resolve();
        }, 1000)
    })
}
log(1)
.then(function() {
    return log(2);//返回Promise
})
.then(function() {
    return log(3);
})
//用Promise的方式,通过链式的风格,让代码更加优雅,
即使继续打印4、5、6...也依旧风格清晰保持比较好的的可读性。

所以,什么是Promise呢?
看了小栗子应该就比较好理解下面这句比较正式的描述了。
👉 Promise是抽象异步处理对象以及对其进行各种操作的组件,它的功能是可以将复杂的异步处理轻松地进行模式化。

Promise基本语法

1、创建一个Promise对象

使用new来调用Promise的构造器来进行实例化

📒基本流程如下:

  • new Promise(fn) 返回一个promise对象
  • 在fn 中指定异步等处理
  • 处理结果正常的话,调用resolve(处理结果值)
    处理结果错误的话,调用reject(Error对象)
var promise = new Promise(function(resolve, reject) {
    // 异步处理
    // 处理结束后、调用resolve 或 reject
});

2、了解Promise的状态

new Promise 实例化的promise对象有以下三个状态。

"has-resolution" - Fulfilled
resolve(成功)时。此时会调用 onFulfilled

"has-rejection" - Rejected
reject(失败)时。此时会调用 onRejected

"unresolved" - Pending
既不是resolve也不是reject的状态。
也就是promise对象刚被创建后的初始化状态等

关于上面这三种状态的读法,其中 左侧为在 ES6 Promises 规范中定义的术语, 而右侧则是在 Promises/A+ 中描述状态的术语。

3、Promise.then()

对通过new生成的promise对象为了设置其值在 resolve(成功) / reject(失败)时调用的回调函数可以使用promise.then() 实例方法。

promise.then(onFulfilled, onRejected)
resolve(成功)时
onFulfilled 会被调用

reject(失败)时
onRejected 会被调用

onFulfilled、onRejected 两个都为可选参数。

promise.then 成功和失败时都可以使用。 如果只想对异常进行处理时可以采用 promise.then(undefined, onRejected) 这种方式,只指定reject时的回调函数即可。 不过这种情况下 promise.catch(onRejected)应该是个更好的选择。

【代码示例】

var promise = new Promise(function(resolve, reject){
    resolve("传递给then的值");
});
// 写法一
promise.then(function (value) {
    console.log(value);
}, function (error) {
    console.error(error);
});
// 写法二(使用catch)
promise.then(function (value) {
    console.log(value);
}).catch(function (error) {
    console.error(error);
});

4、使用快捷方式

静态方法Promise.resolve(value)Promise.reject(error)可以认为是 new Promise() 方法的快捷方式。

4.1 Promise.resolve()

比如 Promise.resolve(1)可以认为是以下代码的语法糖。

new Promise(function(resolve){
    resolve(1);
});

在这段代码中的 resolve(1)会让这个promise对象立即进入确定(即resolved)状态,并将 1 传递给后面then里所指定的 onFulfilled 函数。

方法 Promise.resolve(value)的返回值也是一个promise对象,所以我们可以像下面那样接着对其返回值进行 .then 调用。

Promise.resolve(1).then(function(value){
    console.log(value); // 1
});

运用场景:Promise.resolve()在promise对象的初始化或者编写测试代码的时候都非常方便。

4.2 Promise.reject()

比如 Promise.reject(new Error("出错了"))就是下面代码的语法糖形式。

new Promise(function(resolve,reject){
    reject(new Error("出错了"));
});

方法 Promise.reject(error)的返回值也是一个promise对象,所以可以将错误(Error)对象传递到catch里的函数中。

Promise.reject(new Error("BOOM!")).catch(function(error){
    console.error(error);
});

5、Promise.all()

Promise.all 接收一个 promise对象的数组作为参数,当这个数组里的所有promise对象全部变为resolve或reject状态的时候,它才会去调用 .then 方法。

运用场景: 批量请求

【代码示例】

var p1 = Promise.resolve(1),
    p2 = Promise.resolve(2),
    p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(function (results) {
    console.log(results);  // [1, 2, 3]
});

传递给 Promise.all 的promise并不是一个个的顺序执行的,而是同时开始、并行执行的。
可以从以下例子的运行结果看出:

// `delay`毫秒后执行resolve
function timerPromisefy(delay) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(delay);
        }, delay);
    });
}
var startDate = Date.now();
// 所有promise变为resolve后程序退出
Promise.all([
    timerPromisefy(1),
    timerPromisefy(32),
    timerPromisefy(64),
    timerPromisefy(128)
]).then(function (values) {
    console.log(Date.now() - startDate + 'ms');
    // 約128ms
    console.log(values);    // [1,32,64,128]
});

6、Promise.race()

Promise.race只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理。

【代码示例】

var p1 = Promise.resolve(1),
    p2 = Promise.resolve(2),
    p3 = Promise.resolve(3);
Promise.race([p1, p2, p3]).then(function (value) {
    console.log(value);  // 1
});

进一步分析, 当第一个promise对象变为确定(FulFilled)状态后,它之后的promise对象是否还在继续运行。

// `delay`毫秒后执行resolve
function timerPromisefy(delay) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            console.log(delay)
            resolve(delay);
        }, delay);
    });
}
// 任何一个promise变为resolve或reject 的话程序就停止运行
Promise.race([
    timerPromisefy(1),
    timerPromisefy(10),
    timerPromisefy(20),
]).then(function (value) {
    console.log(value);    // => 1
});
//整体运行结果
1
1
10
20

执行上面代码,我们会看到setTimeout 方法都会执行完毕, console.log 也会分别输出它们的信息。
也就是说, Promise.race 在第一个promise对象变为Fulfilled之后,并不会取消其他promise对象的执行。

Promise实例练习

考虑本文一开始的场景,进行小进阶练习,实现每隔一秒依次输出1-10。

//利用for循环 避免一长串的.then写法
function log(i) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log(i)
            resolve();
        }, 1000)
    })
}
function printN(n) {
    var p = log(1);
    for (var i = 2; i <= n; i++) {
        var a = function(x) { // 注意回调函数闭包的问题
            return function() {
                return log(x);
            }
        }
        p = p.then(a(i));
    }
}
printN(10);

主要参考: JavaScript Promise迷你书