理解 JavaScript Promise

介绍

Javascript Promises可能难以理解。所以,我想写下我对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);
};

让我们把承诺串联起来。你,孩子,只能在showOff承诺之后开始willIGetNewPhone承诺。

// 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.

输出

在等待妈妈的承诺(新手机)时,您不会停止玩游戏。这就是我们所说的异步:代码将运行而不会阻塞或等待结果。任何需要等待承诺进行的东西都被放入.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。另一个著名的承诺库是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();
})();

Promise 以及何时使用它们

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

普通函数 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 之前,我们使用了回调。回调是您在获得返回结果时调用的函数。让我们修改前面的示例以接受回调。

// 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)
    });

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

可观察对象

在您接受 Promise 之前,有一些东西可以帮助您处理名为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。根据情况,这三个因素都可以影响您的发展。

觉得文章有用?

点个广告表达一下你的爱意吧 !😁