了解JavaScript承诺

来自菜鸟教程
跳转至:导航、​搜索

介绍

Javascript Promises 可能很难理解。 因此,我想写下我对 Promise 的理解方式。

理解 Promise

简短的承诺:

“想象一下,你是一个 孩子。 你妈妈 向你承诺 下周她会给你买一部 新手机 。”

不知道 直到下周你才能拿到那部手机。 你妈妈可以真的给你买一部全新的手机,或者她不会

那是 承诺 。 一个承诺具有三种状态。 他们是:

  1. 待定:你不知道你是否会得到那部手机
  2. 实现:妈妈很高兴,她给你买了一部全新的手机
  3. 被拒绝:妈妈不开心,她不给你买手机

创建一个承诺

让我们将其转换为 JavaScript。

// ES5: Part 1

var isMomHappy = false;

// Promise
var willIGetNewPhone = new Promise(
    function (resolve, reject) {
        if (isMomHappy) {
            var phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone); // fulfilled
        } else {
            var reason = new Error('mom is not happy');
            reject(reason); // reject
        }

    }
);

代码本身就很有表现力。

下面是 Promise 语法的正常外观:

// promise syntax look like this
new Promise(function (resolve, reject) { ... } );

消费承诺

现在我们有了 Promise,让我们使用它:

// ES5: Part 2

var willIGetNewPhone = ... // continue from part 1

// call our promise
var askMom = function () {
    willIGetNewPhone
        .then(function (fulfilled) {
            // yay, you got a new phone
            console.log(fulfilled);
             // output: { brand: 'Samsung', color: 'black' }
        })
        .catch(function (error) {
            // oops, mom didn't buy it
            console.log(error.message);
             // output: 'mom is not happy'
        });
};

askMom();

让我们运行示例并查看结果!

演示:https://jsbin.com/nifocu/1/edit?js,console

链接承诺

Promise 是可链接的。

假设你,孩子,承诺你的朋友,当你妈妈给你买一部新手机时,你会给他们新手机。

那是另一个承诺。 让我们写吧!

// ES5

// 2nd promise
var showOff = function (phone) {
    return new Promise(
        function (resolve, reject) {
            var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

            resolve(message);
        }
    );
};

注意:我们可以通过编写如下来缩短上面的代码:

// shorten it

// 2nd promise
var showOff = function (phone) {
    var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

    return Promise.resolve(message);
};

让我们链接承诺。 你这个孩子,只能在 willIGetNewPhone 承诺之后开始 showOff 承诺。

// call our promise
var askMom = function () {
    willIGetNewPhone
    .then(showOff) // chain it here
    .then(function (fulfilled) {
            console.log(fulfilled);
         // output: 'Hey friend, I have a new black Samsung phone.'
        })
        .catch(function (error) {
            // oops, mom don't buy it
            console.log(error.message);
         // output: 'mom is not happy'
        });
};

这就是你可以链接承诺的方式。

Promise 是异步的

Promise 是异步的。 让我们在调用 Promise 之前和之后记录一条消息。

// call our promise
var askMom = function () {
    console.log('before asking Mom'); // log before
    willIGetNewPhone
        .then(showOff)
        .then(function (fulfilled) {
            console.log(fulfilled);
        })
        .catch(function (error) {
            console.log(error.message);
        });
    console.log('after asking mom'); // log after
}

预期输出的顺序是什么? 你可能期望:

1. before asking Mom
2. Hey friend, I have a new black Samsung phone.
3. after asking mom

但是,实际的输出顺序是:

1. before asking Mom
2. after asking mom
3. Hey friend, I have a new black Samsung phone.

在等待妈妈的承诺(新手机)时,您不会停止玩耍。 这就是我们所说的异步:代码将运行而不会阻塞或等待结果。 任何需要等待 Promise 进行的东西都放在 .then 中。

这是 ES5 中的完整示例:

// ES5: Full example

var isMomHappy = true;

// Promise
var willIGetNewPhone = new Promise(
    function (resolve, reject) {
        if (isMomHappy) {
            var phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone); // fulfilled
        } else {
            var reason = new Error('mom is not happy');
            reject(reason); // reject
        }

    }
);

// 2nd promise
var showOff = function (phone) {
    var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

    return Promise.resolve(message);
};

// call our promise
var askMom = function () {
    willIGetNewPhone
    .then(showOff) // chain it here
    .then(function (fulfilled) {
            console.log(fulfilled);
            // output: 'Hey friend, I have a new black Samsung phone.'
        })
        .catch(function (error) {
            // oops, mom don't buy it
            console.log(error.message);
            // output: 'mom is not happy'
        });
};

askMom();

ES5、ES6/2015、ES7/Next 中的 Promise

ES5 - 主流浏览器

如果您包含 Bluebird 承诺库,则演示代码可在 ES5 环境(所有主要浏览器 + NodeJs)中运行。 这是因为 ES5 不支持开箱即用的 Promise。 另一个著名的 promise 库是 Kris Kowal 的 Q

ES6 / ES2015 - 现代浏览器,NodeJs v6

演示代码开箱即用,因为 ES6 原生支持 Promise。 另外,使用 ES6 函数,我们可以进一步简化代码,用箭头函数,使用 constlet

这是 ES6 代码中的完整示例:

//_ ES6: Full example_

const isMomHappy = true;

// Promise
const willIGetNewPhone = new Promise(
    (resolve, reject) => { // fat arrow
        if (isMomHappy) {
            const phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone);
        } else {
            const reason = new Error('mom is not happy');
            reject(reason);
        }

    }
);

// 2nd promise
const showOff = function (phone) {
    const message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';
    return Promise.resolve(message);
};

// call our promise
const askMom = function () {
    willIGetNewPhone
        .then(showOff)
        .then(fulfilled => console.log(fulfilled)) // fat arrow
        .catch(error => console.log(error.message)); // fat arrow
};

askMom();

请注意,所有 var 都替换为 const。 所有的 function(resolve, reject) 都被简化为 (resolve, reject) =>。 这些变化带来了一些好处。

ES7 - 异步/等待

ES7 引入了 asyncawait 语法。 它使异步语法更容易理解,没有 .then.catch

用 ES7 语法重写我们的例子:

// ES7: Full example
const isMomHappy = true;

// Promise
const willIGetNewPhone = new Promise(
    (resolve, reject) => {
        if (isMomHappy) {
            const phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone);
        } else {
            const reason = new Error('mom is not happy');
            reject(reason);
        }

    }
);

// 2nd promise
async function showOff(phone) {
    return new Promise(
        (resolve, reject) => {
            var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

            resolve(message);
        }
    );
};

// call our promise in ES7 async await style
async function askMom() {
    try {
        console.log('before asking Mom');

        let phone = await willIGetNewPhone;
        let message = await showOff(phone);

        console.log(message);
        console.log('after asking mom');
    }
    catch (error) {
        console.log(error.message);
    }
}

// async await it here too
(async () => {
    await askMom();
})();

承诺以及何时使用它们

为什么我们需要承诺? 世界在承诺面前是怎样的? 在回答这些问题之前,让我们回到基本面。

普通函数 VS 异步函数

我们来看看这两个例子。 这两个示例都执行两个数字的相加:一个使用普通函数相加,另一个远程相加。

两个数字相加的普通函数

// add two numbers normally

function add (num1, num2) {
    return num1 + num2;
}

const result = add(1, 2); // you get result = 3 immediately

两个数字相加的异步函数
// add two numbers remotely

// get the result by calling an API
const result = getAddResultFromServer('http://www.example.com?num1=1&num2=2');
// you get result  = "undefined"

如果你用普通函数添加数字,你会立即得到结果。 但是,当你发出远程调用获取结果时,需要等待,并且不能立即得到结果。

您不知道是否会得到结果,因为服务器可能已关闭、响应缓慢等。 您不希望在等待结果时阻止整个过程。

调用 API、下载文件和读取文件是您将执行的一些常见异步操作。

您不需要对异步调用使用 Promise。 在 Promise 之前,我们使用回调。 回调是您在获得返回结果时调用的函数。 让我们修改前面的示例以接受回调。

// add two numbers remotely
// get the result by calling an API

function addAsync (num1, num2, callback) {
    // use the famous jQuery getJSON callback API
    return $.getJSON('http://www.example.com', {
        num1: num1,
        num2: num2
    }, callback);
}

addAsync(1, 2, success => {
    // callback
    const result = success; // you get result = 3 here
});

后续异步操作

我们不想一次添加一个数字,而是要添加三次。 在正常功能中,我们会这样做:-

// add two numbers normally

let resultA, resultB, resultC;

 function add (num1, num2) {
    return num1 + num2;
}

resultA = add(1, 2); // you get resultA = 3 immediately
resultB = add(resultA, 3); // you get resultB = 6 immediately
resultC = add(resultB, 4); // you get resultC = 10 immediately

console.log('total' + resultC);
console.log(resultA, resultB, resultC);

这就是回调的样子:

// add two numbers remotely
// get the result by calling an API

let resultA, resultB, resultC;

function addAsync (num1, num2, callback) {
    // use the famous jQuery getJSON callback API
    // https://api.jquery.com/jQuery.getJSON/
    return $.getJSON('http://www.example.com', {
        num1: num1,
        num2: num2
    }, callback);
}

addAsync(1, 2, success => {
    // callback 1
    resultA = success; // you get result = 3 here

    addAsync(resultA, 3, success => {
        // callback 2
        resultB = success; // you get result = 6 here

        addAsync(resultB, 4, success => {
            // callback 3
            resultC = success; // you get result = 10 here

            console.log('total' + resultC);
            console.log(resultA, resultB, resultC);
        });
    });
});

演示:https://jsbin.com/barimo/edit?html,js,console

由于深度嵌套的回调,这种语法对用户不太友好。

避免深度嵌套的回调

Promise 可以帮助您避免深度嵌套的回调。 让我们看一下同一个例子的 Promise 版本:

// add two numbers remotely using observable

let resultA, resultB, resultC;

function addAsync(num1, num2) {
    // use ES6 fetch API, which return a promise
    // What is .json()? https://developer.mozilla.org/en-US/docs/Web/API/Body/json
    return fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
        .then(x => x.json()); 
}

addAsync(1, 2)
    .then(success => {
        resultA = success;
        return resultA;
    })
    .then(success => addAsync(success, 3))
    .then(success => {
        resultB = success;
        return resultB;
    })
    .then(success => addAsync(success, 4))
    .then(success => {
        resultC = success;
        return resultC;
    })
    .then(success => {
        console.log('total: ' + success)
        console.log(resultA, resultB, resultC)
    });

通过 Promise,我们使用 .then 展平回调。 在某种程度上,它看起来更干净,因为没有回调嵌套。 使用 ES7 async 语法,您可以进一步增强此示例。

可观察的

在你接受承诺之前,有一些东西可以帮助你处理异步数据,称为 Observables

让我们看一下使用 Observables 编写的同一个演示。 在这个例子中,我们将使用 RxJS 作为 observables。

let Observable = Rx.Observable;
let resultA, resultB, resultC;

function addAsync(num1, num2) {
    // use ES6 fetch API, which return a promise
    const promise = fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
        .then(x => x.json());

    return Observable.fromPromise(promise);
}

addAsync(1,2)
  .do(x => resultA = x)
  .flatMap(x => addAsync(x, 3))
  .do(x => resultB = x)
  .flatMap(x => addAsync(x, 4))
  .do(x => resultC = x)
  .subscribe(x => {
    console.log('total: ' + x)
    console.log(resultA, resultB, resultC)
  });

Observables 可以做更多有趣的事情。 例如,delay 通过 3 seconds 添加功能只需一行代码或重试,以便您可以重试调用一定次数。

...

addAsync(1,2)
  .delay(3000) // delay 3 seconds
  .do(x => resultA = x)
  ...

您可以在 此处 阅读我的一篇 RxJs 帖子。

结论

熟悉回调和承诺很重要。 了解它们并使用它们。 暂时不要担心 Observables。 这三个因素都可以根据情况影响您的发展。