Promise

什么是Promise

Promise中文意为"诺言",现在对它的历史解析开始了。
JavaScript是单线程的,调用函数执行一个异步操作的时候,通常会塞给这个函数几个callback回调函数,这个函数在未来自主选择在何时调用、调用哪个、传递什么参数。
那如果你不先确定好要如何编写这个callback(比如你还没想清楚要如何使用回调时的参数),你就无法调用这个函数,直到你想清楚了,写好了,你才能调用它,任务才会开始。
一旦你不在callback中作出决定,就调用了这个函数,那么你将永远失去你可以获得的result。

我们用同步来模拟异步,看看其中有什么乾坤

const child_process = require('child_process');

// 模拟一次耗时2s的操作, 得到结果8
function await(millis) {
        child_process.execSync(`sleep ${ millis / 1000 };`);
        return 8;
}

function awaitResult(callback) {
        console.log('开始异步任务');
        var result = await(2000);
        callback(result);
        console.log('完成异步任务');
}

console.log('开始调用异步函数');
awaitResult(result => {
        console.log('结果是' + result);
});
console.log('完成调用异步函数');

/**
开始调用异步函数
开始异步任务
(...2s后)
结果是8
完成异步任务
完成调用异步函数
*/

而我们改用Promise来重写它以后:

const child_process = require('child_process');

// 模拟一次耗时2s的操作, 得到结果8
function await(millis) {
        child_process.execSync(`sleep ${ millis / 1000 };`);
        return 8;
}

function awaitResult() {
        var task = new Promise((resolve, reject) => {
                console.log('开始异步任务'); // Promise构造函数会立即执行这个函数
                var result = await(2000);
                resolve(result);
        });     
        console.log('完成异步任务');
        return task;
}

console.log('开始调用异步函数');
var task = awaitResult();
console.log('完成调用异步函数');
task.then(result => {
        console.log('结果是' + result);
});     

/**
开始调用异步函数
开始异步任务
(...2s后)
完成异步任务
完成调用异步函数
结果是8
*/

相信大家一定可以看出来了,"诺言"的含义:
立刻执行这个任务!成功的话我等一下会告诉你你接下来该怎么办的,把结果给我留好,随时待命。对了,失败的话我也会告诉你该怎么办的,你先做,做完好好等着就是了,说不定你还没做完我就已经告诉你该怎么弄了。

调用者(程序员)承诺:会告诉执行者如何处理result。(! 这个操作是可以重复的)
执行者(Promise)承诺:会在未来某个时机执行调用者给出的函数,当然了,人家最爱的是result,传递给他。

Promise 的承诺

Promise是在何时承诺的呢?关键就在于下面这行代码,resolvereject就是Promise给我们的承诺之星,分别对应成功和失败两种状态,用then()catch()来回应。

resolve(result);

Promise 的状态

Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态。

  • 异步操作未完成(pending)
  • 异步操作成功(fulfilled)
  • 异步操作失败(rejected)

上面三种状态里面,fulfilled和rejected合在一起称为resolved(已定型)。

这三种的状态的变化途径只有两种。

  • 从“未完成”到“成功”
  • 从“未完成”到“失败”

一旦状态发生变化,就凝固了,不会再有新的状态变化。

这也是 Promise 这个名字的由来,它的英语意思是“承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise 实例的状态变化只可能发生一次。

当然了,对于这种说法本人不置可否。就连MDN英文也表示:JavaScript中的承诺表示已经发生的进程,可以与回调函数链接在一起。

因此,Promise 的最终结果只有两种。

  • 异步操作成功,Promise 实例传回一个值(value),状态变为fulfilled。
  • 异步操作失败,Promise 实例抛出一个错误(error),状态变为rejected。

Promise 对象、Promise 对象状态、承诺

现在我们再次强化这种认知:
一个Promise对象被new后立即执行构造时用户传递的函数,这个函数的原型为:(resolve, reject) => {},在调用resolve()reject()之前,Promise处于pending状态,直到有且仅有其中一个状态函数被调用,Promise对象状态被改变,成为resolved状态,及fulfilled或rejected状态之一。
resolve(...param)导致Promise进入fulfilled状态,对Promise # then((...param) => {})的每次调用都将得到承诺被回调,并且传递对应的param。成功状态下的Promise # catch()调用就像被忽略了一样。
reject(...param)导致Promise进入rejected状态,对Promise # catch((...param) => {})的每次调用都将得到承诺被回调,并且传递对应的param。then()的处理请看链式调用。

链式调用

容易迷糊的地方来了! then()和catch()也返回新的Promise对象!
我们需要研究then、catch的机制。

我们先看看一个链式调用的例子:

"use strict";

function a(state) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (state)
                resolve('Task A succeed');
            reject('Task A failed');
        }, 800);
    });
}

function b(state) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (state)
                resolve('Task B succeed');
            reject('Task B failed');
        }, 800);
    });
}

{
    // a进入成功状态,then参数r=>{}被执行,then返回的Promis对象也进入成功状态
    a(true).then(r => {
        console.log('[A]', r); // 800ms后输出:[A] task A succeed
    });
}

{
    // a进入成功状态,catch参数r=>{}不被执行,catch返回的Promise对象直接进入成功状态
    a(true).catch(r => {
        console.log('[B]', r); // 不会打印
    }).then(r => {
        console.log('[B2]', r);
    });
}

{
    // a进入成功状态,该对象没有then方法,没有任何处理
    a(true);
    // a进入失败状态,该对象没有一个catch方法,抛出UnhandledPromiseRejectionWarning异常,不是这个调用抛出的,是Promise内部抛出的
    // a(false); // 会导致异常
    // 给会失败的a一个catch处理方法,UnhandledPromiseRejectionWarning异常消失
    a(false).catch(e => {
        console.log('[C]', e);
    });
    // 在a进入失败状态之前就应该设置catch方法,否则照样报异常
    if (false) {
        let tmp = a(false);
        setTimeout(() => {
            tmp.catch(r => {
                console.log('[C2]', r);
            });
        }, 1000);
    }
    // 失败状态的Promise不会回调then处理方法,不过then返回的Promise对象也进入失败状态,由于父子Promise都没有catch,抛出异常
    if (false)
    a(false).then(r => {
        console.log('[C3]');
    });
    // then进入失败状态后调用catch处理方法,异常得到了处理,父Promise不抛出异常
    a(false).then(r => {
        console.log('[C4]');
    }).catch(e => {});
    // 失败的传递性
    if (false) {
        let tmp = a(false);
        tmp.catch(() => {console.log('失败1');}); // 执行
        tmp.then(() => {console.log('失败3');}).catch(() => {console.log('失败4');}); // tmp失败,但是异常传递给then并得到了catch处理
        tmp.then(() => {console.log('失败2');}); // then失败没有catch,抛出异常
    }
}

{
    // a成功,执行then,由于then处理方法返回一个Promise,then仍然处于pending状态,直到b失败,then失败,下一个then失败,执行catch
    a(true).then(r => b(false)).then(r => {
        console.log('[D]', r);
    }).catch(reason => {
        console.log('失败原因:', reason);
    });
}
  • Promise1.then(r => use(r)).then(r => use(r)).catch(reason => use(reason));
    对于Promise1对象的then调用,当Promise状态定型时,若
    1.失败,则then返回的Promise2失败,...失败原因传递给最后一个then,如果没有被catch则抛出异常
    2.成功,执行then处理方法,如果use(r)返回Promise对象,则等待该对象,这里发生了对象的代理,否则then成功,执行下一个then。

ES6 async await 关键字

有了Promise还不够,我们可以使用async关键字将一个function函数或者箭头函数声明为同步函数,对于一个返回Promise实例的函数调用,使用await关键字获取resolve()函数传递的值,仿佛编写同步代码一样,无需回调函数。

"use strict";

function getData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(888);
        }, 800);
    });
}

async function test() {
    try {
        var result = await getData();
        console.log('computed result:', result);
    } catch(e) {
        console.log('An Error occurred');
    }
}

(function main() {
    test();
    (async () => {
        var n = await getData();
        console.log('computed result:', n);
    })();
})();

直接await new Promise();也是可以的!

    (async () => {
        var n = await new Promise((resolve, reject) => {
            resolve(88);
        });
        console.log('computed result:', n);
    })();

对了,别忘了async函数也返回的是一个Promise对象。

END

posted @ 2020-04-12 22:25  develon  阅读(419)  评论(0编辑  收藏  举报