打开Promise的正确姿势

promise规范和应用 (5)LocalStorage:是前端常用的本地存储机制,能够在不同页面之间存储和读取数据,当LocalStorage的值发生变化时,会触发storage事件,在发送消息时将消息写入LocalStorage,然后在各个页面中通过监听storage事件来接收消息。(3)Shared Worker:也是一个在后台运行的脚本,但与Service Worker不同的是可以被多个页面共享,需要在页面创建一个Shared Worker实例,并通过它来接受和发送消息。(4)本地存储数据问题; 阅读详情

打开Promise的正确姿势

本文作者:imweb 孙世吉 原文出处:imweb社区 未经同意,禁止转载

引言

最近实践中需要用到大量的异步回调风格代码的编写,作者最近处于同步编程风格转为异步编程风格的状态。同时第一时间遇到了下面提到的代码,第一直觉就是该代码肯定有问题!但是问题在哪里有讲不出来,感觉非常蛋疼与疑惑。先上当时遇到的代码:

// 删除,先检查是否存在,存在再执行真正的删除动作
function del() {
    // 查找
    return find().then(function(resultOfFind) {
        // 如果没找到,直接返回
        if (!resultOfFind) {
            return false;
        }
        // 执行真正做删除操作的方法
        return reallyDelete();
    }, function(err) {
        //  处理错误情况
        handle(err)
    })
}

function deleteItem(req, res) {
    // 删除
    del().then(function(resultOfDelete) {
        // 判断是没找到或者是真正做删除操作的结果,进行处理
        //  ...
        res(resultOfDelete)
    }, function(err) {
        // 处理错误情况
        // ...
        res(err)
    })
}

上面代码做的事情很简单:
1、删除前检查是否存在;
2、存在则执行删除操作 / 不存在则直接返回;
3、使用删除的结果 / 处理删除产生的错误,返回响应;

你看出来哪里可能出现问题了吗?

OK,这里容作者买一个小小的关子。同学们如果有疑问,那就继续往下阅读吧。
看出问题的大神们也请不吝指教。
让作者为你带来打开Promise的正确姿势,让你使用Promise的时候用的更爽,后人接手你的代码看的更爽,也避免出现莫名其妙的问题而无法对问题进行定位的情况。

目录

1、 Promise基础介绍
2、 Promise与金字塔问题
3、 Promise与循环
4、 resolve(value) VS resolve(promise)
5、 then返回的promise实例
6、 Promise与错误处理
7、 Promise状态透传

1. Promise基础介绍

Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。
本文所描述的Promise指Promises/A+规范定义的Promise,可参考Promise/A+,一个可靠的可共同协作的JavaScript Promise开放标准。

1.1 常见的用法

var promise = new Promise(function(resolve, reject) {
    // ... some code
    if ( /* 异步操作成功 */ ) {
        resolve(value);
    } else {
        reject(error);
    }
});

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

简单来说:
1、Promise构造方法接受一个方法作为参数,该方法传入两个参数,resolve和reject。
2、resolve用来将Promise对象的状态置为成功,并将异步操作结果value作为参数传给成功回调函数。
3、reject用来将Promise对象的状态置为失败,并将异步操作错误error作为参数传给失败回调函数。
4、then方法绑定两个回调函数,第一个用来处理Promise成功状态,第二个用来处理Promise失败状态。

1.2 Promise 状态

Promise对象有三种状态:

  • Pending(进行中)
  • Fulfilled(已完成,又称为Resolved)
  • Rejectd(已失败)


如上图所示,Promise对象有两个特点:
1、对象状态只由异步操作结果决定。resolve方法会使Promise对象由pendding状态变为fulfilled状态;reject方法或者异常会使得Promise对象由pendding状态变为rejected状态。Promise状态变化只有上图这两条路径。
2、对象状态一旦改变,任何时候都能得到这个结果。即状态一旦进入fulfilled或者rejected,promise便不再出现状态变化,同时我们再添加回调会立即得到结果。这点跟事件不一样,事件是发生后再绑定监听,就监听不到了。

举个例子:

var promise = new Promise(function(resolve, reject) {
    resolve('value'); //1
    // reject('error'); //2
    // throw Error('exception'); //3
    // return 'return' //4
});

promise.then(function(value) {
    console.log('fulfilled', value)
}, function(error) {
    console.log('rejected', error)
});
// 上面的代码中1-4处代码的调用分别输出:
// fulfilled value  
// rejected error  
// rejected Error: exception(…)  
// [没有输出]

// 测试状态变更后才绑定事件
// setTimeout(function() {
//     promise.then(function(value) {
//         console.log('fulfilled1', value)
//     })
// }, 1000)

当然也可以参考Promise/A+里面的定义:

2.1.Promise状态

  promise状态为pending, fulfilled和rejected中的其中一种。

  2.1.1.当promise状态为pending时:

    2.1.1.1.promise的状态可以转换为fulfilled或rejected。

  2.1.2.当promise状态为fulfilled时:

    2.1.2.1.无法转换为其他状态。

    2.1.2.2.必须有一个不可改变的值作为onFulfilled事件处理函数的入参

  2.1.3.当promsie状态为rejected时:

    2.1.3.1.无法转换为其他状态。

    2.1.3.2.必须有一个不可改变的值作为onRejected事件处理函数的入参

2. Promise与金字塔问题

金字塔问题指的是我们的程序中如果出现大量的回调任务需要顺序执行时,可能会出现这些代码慢慢向右侧屏幕延伸的问题。通常我们多次异步操作需要依赖上一次异步操作的结果时,我们会这样写。举个例子:

getUserAdmin().then(function(result) {
    if ( /*管理员*/ ) {
        getProjectsWithAdmin().then(function(result) {
            /*根据项目id,获取模块列表*/
            getModules(result.ids).then(function(result) {
                /*根据模块id,获取接口列表*/
                getInterfaces(result.ids).then(function(result) {
                    // ...
                })
            })
        })
    } else {
        //...
    }
})

上面的例子数据有项目-模块-接口这样的层次关系。为了获取接口列表,每一次操作都需要依赖上一个异步操作的结果。你会发现使用顺序调用的逻辑这样写使得代码层次嵌套过深,逻辑不清晰,很难进行阅读。如果我们像使用回调一样使用Promise,虽然结果是正确的,但是这完全没有利用到Promise的优势。

我们应该像下面这样写:

getUserAdmin().then(function(reult) {
    if ( /*管理员*/ ) {
        return getProjectsWithAdmin();
    } else {
        return getProjectsWithUser();
    }
}).then(function(result) {
    /*获取project id列表*/
    return getModules(result.ids);
}).then(function(result) {
    /*获取project id列表*/
    return getInterfaces(result.ids)
}).then(function(result) {
    // ...
})

如果你再将这些操作封装到一个命名函数中,你就会得到下面这样的代码:

getUserAdmin()
    .then(getProjects)
    .then(getModules)
    .then(getInterfaces)
    .then(procResult)

是不是觉得赏心悦目,作者第一次看到这样的代码时简直惊为天人,这简直是在写诗好吗?

这种写法被称为 composing promises ,是 promises 的强大能力之一。这样写可以带来好处:

  • 清晰的代码结构。
  • 避免始料不及的错误。进行快速的问题定位,避免难以调试更甚至于失败了而没有任何反馈。

关于第二点,会在下面错误处理中进行说明。

3. Promise与循环

上面讲到了异步操作的顺序执行,那如果我们需要同时执行一组异步操作呢?
举个例子,我们需要删除指定项目ID下的所有子模块。有的同学可能会这样写:

getModules(projectID).then(function(modules) {
    modules.forEach(function(module) {
        removeModule(module.moduleID);
    })
    // A
}).then(function(result) {
    // ...
})

这里存在一个问题,就是A位置并不会等待所有的removeModule方法结束,而是直接返回undefined,这意味着后面的方法即不会等待删除动作结束也无法获得删除动作的结果,所以你没办法保证删除动作已经完成。 关于在then方法绑定的回调函数中的返回值,我们会在第五节中进行讨论。

那我们怎么保证所有异步操作都成功了呢?

Promise提供了一个很方便的方法叫做Promise.all。我们可以这样做:

getModules(projectID).then(function(modules) {
    var tasks = [];
    modules.forEach(function(module) {
        tasks.push(removeModule(module.moduleID));
    })
    return Promise.all(tasks);// 注意这里一定要有return
}).then(function(result) {
    // ...
})

Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。
Promise.all方法接受一个数组作为参数,数组里的元素都是Promise对象的实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。)

  • 当该数组里的所有Promise实例都进入Fulfilled状态,Promise.all返回的实例才会变成Fulfilled状态。并将Promise实例数组的所有返回值组成一个数组,传递给Promise.all返回实例的回调函数。
  • 当该数组里的某个Promise实例都进入Rejected状态,Promise.all返回的实例会立即变成Rejected状态。并将第一个rejected的实例返回值传递给Promise.all返回实例的回调函数。

Promise.race方法跟Promise.all方法差不多。唯一的区别在于该方法返回的Promise实例并不会等待所有Proimse都跑完,而是只要有一个Promise实例改变状态,它就跟着改变状态。并使用第一个改变状态实例的返回值作为返回值。

当然前述的代码还可以更优雅一点:

getModules(projectID).then(function(modules) {
    return Promise.all(modules.map(module) { // 注意这里一定要有return
        return removeModule(module.moduleID); // 注意这里一定要有return
    });
}).then(function(result) {
    // ...
})

4. resolve(value) VS resolve(promise)

我们会在异步操作成功时调用resolve函数,其作用是将Promise对象的状态从Pending变为Resolved,并将异步操作的结果,作为参数传递给Fulfilled状态的回调函数。

而我们传入resolve的值实际上并不一定出现在Fulfilled状态的回调函数中,为什么呢?

我们来先看一个使用resolve传递操作结果的例子:

var d = new Date();

var promise = new Promise(function(resolve, reject) {
    // 一秒后进入resolve,并传递值
    setTimeout(resolve, 1000, 'resolve from promise');
});

// 绑定回调函数
promise.then(
    result => console.log('result:', result, new Date() - d),
    error => console.log('error:', error)
)
// result: resolve from promise 1002

大约过了一秒左右,我们可以看到在fulfilled状态的回调方法中,我们打印出了上面注释中的内容。我们能够通过resolve方法传递操作的结果,然后在回调方法中使用这些结果。

如果我们在resolve中传入一个Promise实例呢?

var d=new Date();

// 创建一个promise实例,该实例在2秒后进入fulfilled状态
var promise1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 2000, 'resolve from promise 1');
});

// 创建一个promise实例,该实例在1秒后进入fulfilled状态
var promise2 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 1000, promise1); // resolve(promise1)
});

promise2.then(
    result => console.log('result:', result,new Date()-d),
    error => console.log('error:', error)
)

上面的例子中,你可能觉得再经过一秒左右后会打印出promise1实例。实际上上面的代码最后打印出:

result: resolve from promise 1 2002


OK,我们看一下Promise/A+是怎么说的:
[[Resolve]](promise, x)中

2.3.2.如果x是一个promise实例, 则以x的状态作为promise的状态

  2.3.2.1.如果x的状态为pending, 那么promise的状态也为pending, 直到x的状态变化而变化。

  2.3.2.2.如果x的状态为fulfilled, promise的状态也为fulfilled, 并且以x的不可变值作为promise的不可变值。

  2.3.2.3.如果x的状态为rejected, promise的状态也为rejected, 并且以x的不可变原因作为promise的不可变原因。

2.3.4.如果x不是对象或函数,则将promise状态转换为fulfilled并且以x作为promise的不可变值。

简单来说呢,就是因为promise2中调用了resolve(promise1),此时promise1的状态会传递给promise2,或者说promise1的状态决定了promise2的状态。所以当promise1进入fulfilled状态,promise2的状态也变为fulfilled,同时将promise1自己的不可变值作为promise2的不可变值,所以promise2的回调函数打印出了上述结果。promise1进入rejected状态的结果,同学们可以自己试一试。

而当我们resolve(value)的时候就遵循Promise/A+中的2.3.4条规范,将value传递给了fulfilled状态的回调函数。

另外,通过这里例子我们也可以发现。运行时间是2秒而不是3秒。也就是说Promise新建后就会立即执行。

ps:resolve方法传入的实参不限于值类型或者Promise实例。更多内容请参考Promise/A+。

5. then返回的promise实例

then方法返回的是一个新的Promise实例
then方法返回的是一个新的Promise实例
then方法返回的是一个新的Promise实例
重要的事情要说三遍。这也是我们能够使用第二节中的链式写法的最重要原因。举个例子:

var d = new Date();

var promise = new Promise(function(resolve, reject) {
    setTimeout(resolve, 1000, 'resolve from promise');
});


var promise2 = promise.then(function(result) {
    console.log(result);
});
//promise.then() 返回的是一个新的Promise实例
promise.tag = '1';
console.log(promise)
console.log(promise2)
// 打印结果:
// Promise {tag: "1", [[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
// Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
// resolve from promise

当然你可以在同一个Promise实例中多次调用.then绑定回调方法,当该Promise实例状态变化时,将按调用.then的顺序执行回调方法。

那在.then绑定的回调方法onFulfilled和onRejected中,不同的返回值对后续链式有什么影响呢?

5.1 回调方法返回 值
var d = new Date();

var promise1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 1000, 'resolve from promise1');
    // setTimeout(reject, 1000, 'reject from promise1');
});

var promise2 = promise1.then(function(result) {
    console.log('promise1.then(resolve):', result);
    return result;
}, function(error) {
    console.log('promise1.then(reject):', error);
    return error;
});

promise2.then(
    result => console.log('result:', result, new Date() - d),
    error => console.log('error:', error, new Date() - d)
)

// promise1.then(resolve): resolve from promise1
// result: resolve from promise1 1012

通过运行上面的例子,我们会发现promise的状态无论是fulfilled或者rejected,其绑定的.then方法返回的Promise实例(即promise2)都只会执行它的onFulfilled回调方法。

5.2 回调方法返回 promise实例
var d = new Date();

var promise1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 1000, 'resolve from promise1');
});

var promise2 = promise1.then(function(result) {
    console.log('promise1.then(resolve):', result);
    return new Promise(function(resolve, reject) {
        setTimeout(resolve, 2000, 'from new promise');
    });
})
promise2.then(
    result => console.log('result:', result, new Date() - d),
    error => console.log('error:', error, new Date() - d)
)
// promise1.then(resolve): resolve from promise1
// result: from new promise 3021

上面的例子中,promise1的回调方法onFulfilled返回的是一个新的promise实例,该实例在2秒后进入fulfilled状态。
运行该例子,我们会发现最后promise2的回调方法近三秒钟后才执行。也就是promise1和promise2的总共运行时间,为什么呢?
同时第二行打印的内容来自于promise2回调方法中返回的新Promise实例,这就是怎么一个过程呢?

Promise/A+规定

2.2.7. then方法必须返回一个promise实例

promise2 = promise1.then(onFulfilled, onRejected);

  2.2.7.1. 如果 onFulfilled 或 onRejected 函数返回值为x,那么执行Promise处理过程 [[Resolve]](promise2, x)。

  2.2.7.2. 如果 onFulfilled 或 onRejected 函数抛出异常e,那么promise2将执行 reject(e)。

查看其中2.2.7.1的规定,我们会发现在onFulfilled或者onRejected中,无论是return值或者return Promise实例,实际上都是去调用[[Resolve]](promise2, x)。

当我们在promise1的回调方法中返回x的时候,相当于调用promise2.resolve(x)。

所以结合本文第四节的内容,我们可以就知道 :
当x为值的时候,promise2直接进入fulfilled状态,无论promise1的状态是fulfilled或者是rejected,并把x传给onFulfilled回调方法;
当x为promise实例的时候,x的状态决定了promise2的状态。

5.3 回调方法没有返回语句

如果promise1的回调方法中没有返回语句,那promise2的回调方法中会打印什么内容呢?

var d = new Date();

var promise1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 1000, 'resolve from promise1');
});

var promise2 = promise1.then(function(result) {
    console.log('promise1.then(resolve):', result);
})

promise2.then(
    result => console.log('result:', result, new Date() - d),
    error => console.log('error:', error, new Date() - d)
)
// promise1.then(resolve): resolve from promise1
// result: undefined 1009

当js函数中没有返回语句的时候,相当于是return undefined。也就是说相当调用了promise2.resolve(x),而这里的x为undefined,所以我们在promise2的回调方法中打印出了undefined。

所以这里作者给的建议是:在回调方法中一定要有return语句,放弃在回调方法中使用return,相当于放弃获取在该回调方法中的所有操作结果。

6. Promise与错误处理

.then(onFulfilled,onRejected)
.then传入的第二个回调方法在Promise实例状态变为rejected的时候会被调用,通常用于处理异步操作失败的情况。
让我们再来瞄一眼本文开头的代码:

// 删除,先检查是否存在,存在再执行真正的删除动作
function del() {
    // 查找
    return find().then(function(resultOfFind) {
        // 如果没找到,直接返回
        if (!resultOfFind) {
            return false;
        }
        // 执行真正做删除操作的方法
        return reallyDelete();
    }, function(err) {
        //  处理错误情况 —— A
        handle(err)
    })
}

function deleteItem(req, res) {
    // 删除
    del().then(function(resultOfDelete) {
        // 判断是没找到或者是真正做删除操作的结果,进行处理 —— C
        //  ...
        res(resultOfDelete)
    }, function(err) {
        // 处理错误情况 ——B
        // ...
        res(err)
    })
}

经过上面前五节的讲解,想必你一定可以找出问题:
A位置:如果find方法返回的promise实例如果进入rejected状态,经过handle的处理后,没有明确的将错误返回,或者将该promise实例置为rejected状态。这种情况在第5.3节已经提过了,这里del方法返回的promise实例直接就是fulfilled状态,而且传入的回调参数为undefined,也就是说这边的resultOfDelete为undefined。这可能导致后续的回调函数没办法正常的工作,而且对这种异常情况完全没有做处理。

B位置:通过对A位置的分析,相比你也发现B位置的错误处理实际上并不能很好的处理到promise实例的异常。除了上面的情况下B位置无法处理到来自del方法的rejected状态,当C位置出现错误的时候,B位置的错误处理代码也同样无法处理。

那么我们应该怎么做呢?
首先在A位置,如果你需要在这里对异常或者rejected状态做操作,例如记录系统日志。请考虑后续是否可能会用到del的操作结果,如果存在这种情况(例如上文我们的用法),那么一定要将该状态暴露到下一个promise中。

function del() {
    // 查找
    return find().then(function(resultOfFind) {
        // ...
    }, function(err) {
        handle(err);
        return Promise.reject(err); // 这里不要遗漏return关键字
    })
}

但是这样又可能出现对同一个错误多次处理的情况。
作者这里推荐使用.catch方法。

.catch

Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。

// 下面两种写法是等价的
somePromise.catch(function(err) {
    //...
})
somePromise.then(null, function(err) {
    //...
})

但是下面两段代码是不等价的

// 1
somePromise.then(function() {
    return someOtherPromise();
}, function(err) {
    //...
})
// 2
somePromise.then(function() {
    return someOtherPromise();
}).catch(function(err) {
    //...
})

为什么呢?举一个例子你可能就明白了:

somePromise.then(function() {
    throw new Error('oh no');
}, function(err) {
    //...
})
somePromise.then(function() {
    throw new Error('oh no');
}).catch(function(err) {
    //...
})

你会发现上一种写法你没办法处理onFulfilled回调函数抛出的异常,而第二种是可以处理这种情况的异常的。所以作者推荐大家都是用catch来处理失败情况,而不是then的第二个参数。你可以在你的promise最后都加上一个catch,以处理你可能没有察觉到的错误情况。
当然有些情况下我们不得不使用then的第二个参数,这时候你就需要注意是否存在别人调用这个方法的可能,并做好错误处理。
所以文章开头出现的问题可以这样解决:

//删除,先检查是否存在,存在再执行真正的删除动作
function del() {
    // 查找
    return find().then(function(resultOfFind) {
        // 如果没找到,直接返回
        if (!resultOfFind) {
            return false;
        }
        // 执行真正做删除操作的方法
        return reallyDelete();
    })
}

function deleteItem(req, res) {
    // 删除
    del().then(function(resultOfDelete) {
        // 判断是没找到或者是真正做删除操作的结果,进行处理
        //  ...
        res(resultOfDelete)
    }).catch(function(err) {
        // 处理错误情况
        // ...
        res(err)
    })
}

7.Promise状态透传

在看上一节内容的时候你可能会有疑问,为什么find.then中没有设置onRejected回调函数对rejected状态进行处理,后面可以使用catch直接捕获之前的结果呢?
我们来先看个例子:

var d = new Date();
// 一秒后进入rejected状态
var promise1 = new Promise(function(resolve, reject) {
    setTimeout(reject, 1000, 'reject from promise1');
});
// 只绑定了onFulfilled回调
var promise2 = promise1.then(result => {
    console.log('promise1.then(resolve):', result);
});
// 绑定了onFulfilled和onRejected。(这里为了演示,正常情况下,建议使用catch处理rejected状态)
promise2.then(
    result => console.log('result:', result, new Date() - d),
    error => console.log('error:', error, new Date() - d)
);

//error: reject from promise1 1004

是的,正如我们所想,promise2的onRjected回调方法正确的处理了来自promise1的rejected状态。
也就是说promise1的rejected状态以及不可变原因都传递给了promise2。

来看Promise/A+是怎么说的:

2.2.7. then方法必须返回一个promise实例

promise2 = promise1.then(onFulfilled, onRejected);

  2.2.7.3. 如果 promise1的 onFulfilled 不是函数,那么promise1的不可变值将传递到promise2并作为promise2的不可变值。

  2.2.7.4. 如果 promise1的 onRejected不是函数,那么promise1的不可变原因将传递到promise2并作为promise2的不可变原因,并作为promise2的 onRejected 的入参。

这就是Promise的状态透传特点,如果当前的promise实例没有绑定回调函数,或者绑定的不是函数,那么当前实例就会把其状态以及不可变值或者不可变原因传递给当前实例调用.then方法返回的新promise实例。
在上述例子中就表现为,promise1把它的不可变原因以及rejected状态传递给了promise2,所以promise2的onRejected回调方法就把promise1中reject的内容打印出来了。
这也是为什么我们在第六节提到可以使用最后的catch捕获之前没有进行处理的rejected状态的原因了。

最后

本文从项目中遇到的代码出发,先是讲解Promise链式调用与循环调用的常见用法,接着结合Promise/A+规范,对Promise使用过程中容易出现的疑点进行了剖析与验证,并解决了项目代码的问题。

请大家记住三点:

  • 回调方法中一定要使用return语句,避免调用者丢失其处理状态与结果。
  • 在promise实例的最后使用catch方法,用来做整体的异常捕获与处理。
  • 利用Promise.then方法对回调函数返回结果的封装,写出清晰漂亮的链式调用代码。

参考资料

Promise/A+
promise对象
关于promises,你理解了多少?

逆向实战:基于内存态匿名段分析的 DexProtector 检测绕过策略 本文深入探讨了针对DexProtector加固方案的Frida检测绕过策略。通过分析运行时动态加载到匿名内存段的核心检测逻辑,提出了一套基于内存态分析、定位并转储匿名可执行段,进而通过精准Hook与“等式化替换”等技术实现稳定绕过的实战方法。该方法不仅适用于DexProtector,也对采用类似内存隐藏技术的加固方案具有参考价值。 阅读详情

相关推荐

S32K1XX调试--快速定位HardFault

1、背景 程序运行,发现程序跑飞到HardFault,但不清楚为什么会跑到HardFault中断处理函数去。 2、分析 要想知道为什么会跑到HardFault_Handler中去,就很有必要了解一下,程序上一条执行的是那条命令。 根据汇编指令可以定位上条指令,但如果对汇编指令不是很了解的话,那就很头疼了 根据单步运行也能实现定位,但如果程序量很大,又很杂,可能花费的时间就比较久些。 3、问题查找 提供一种不需要懂汇编指令的查找方法,方法如下: 在中断处理函数中插入以下代码,以及另外定义

ping_devil的博客 4019

Promise的介绍及使用教程

Promise是ES6引入的一种异步编程解决方案,它代表了一个异步操作的最终完成(或失败)及其结果值。

一名全栈开发工程师 731

软件工程期末知识点总结【超详细】

生命周期模型(过程模型):瀑布模型、快速原型模型、增量模型、螺旋模型、喷泉模型。2、实体联系图:1、数据对象2、属性3、联系(1:1、1:N、M:N)3、语句覆盖 就是设计足够的调试用例,使得程序中的每个语句至少执行一次。 5、判定覆盖就是设计足够的测试用例,使得程序中每个判定的取“真”分支和取“假”分支至少都执行一次,判定覆盖又称分支覆盖 6、条件覆盖就是设计足够的测试用例,使得程序判定中的每个条件能获得各种可能的结果。7、软件开发时期通常分成4个阶段:总体设计、详细设计、编码和单元测试、综合测试。

这个人好有趣....hh 1万+

如何拿到promise里的数据以及为什么会出现promise数据

Promise对象有一个.then()方法,它接收两个回调函数作为参数,第一个回调函数用于处理Promise解析(resolved)后的数据,第二个回调函数用于处理Promise被拒绝(rejected)后的错误。在上述示例中,我们定义了一个名为fetchData的函数,它返回一个Promise对象。在上述示例中,我们定义了一个名为getData的async函数,在函数内部使用await关键字等待fetchData()返回的Promise解析,并将解析后的数据赋值给data变量。

z2000ky的博客 5282

csp-j/s

CSP-J/S 是中国计算机学会(CCF)主办的 ‌‌(Certified Software Professional Junior/Senior),是信息学奥赛(NOI)体系的入门级选拔考试,也是国内最具权威性的青少年编程能力认证之一。

gxyxxrYYDS的博客 2028

1、Ka频段双极化低剖面卫通相控阵天线

设计了双馈口叠层十字缝隙耦合天线,采用“双线极化天线 +旋转馈电 + 移相控制”架构,采用双馈口的叠层十字缝隙耦合天线,并以 2 x2 阵列为主体,旋转排布实现二次圆极化,同时极化可切换,通过多层 PCB 工艺实现天线层、控制/电源层功分网终层、芯片层一体化集成,研制出 64 阵元的圆极化可切换发射天线,测试结果表明:天线工作 频段为 27.5 ~31 GHz,可实现60°扫描,法向轴比

qq_43416206的博客 4369

将异步(Promise)方法转化为同步执行

将异步(Promise)方法转化为同步执行

Turn_to_empty的博客 3093

SAP Group Reporting入门指南(8)--业务操作

关于资产负债表类的匹配,系统预设了SGR01和SGR02两个方法,主要区别在于数据的来源范围,SGR01取自ACDOCA和ACDOCU,如果是0F类型的就会直接显示日记账分录(期间是000的除外,这类凭证是年结结转的,系统不再跨年追溯),如果是其他类型就显示合并分录。这里是我觉得SAP还需要优化的地方。基于ICMR的抵消任务中通常将抵销差异细分为交易差异、货币换算差异和其他差异,这在基于重分类抵消任务是无法实现的,基于重分类抵消只知道一个差异总额,很难从合并抵消凭证看出差异的来源,也没有审计线索可言。

bflyfish的博客 276

js 将Promise改为同步操作

js 将Promise改为同步操作。async/await实现。

Misnice的博客 2033

SCADA|KingSCADA通过组合框选择修改变量的值

《SCADA项目中组合框控件的使用方法》 本文详细介绍了SCADA系统中组合框(下拉框)控件的应用方法。通过具体示例演示如何实现:当选择"雷工笔记"时变量值为100,选择"雷工杂谈"时为200,选择"小李食代"时为300的功能。主要实现步骤包括:1)创建项目并添加组合框控件;2)设置Items选项及对应数值;3)创建内存变量;4)使用GetCurrentIndex()和GetItemData()方法;5)在选项变化事件中编写脚本。该控件在SCADA

做有价值的事,积累有价值的笔记! 864

YOLOv11改进策略【Head】| AFPN渐进式自适应特征金字塔,增加针对小目标的检测头(附模块详解和完整配置步骤)

一、本文介绍 本文利用AFPN对YOLOv11的网络模型进行优化提升。AFPN采用渐近式融合及自适应空间融合操作。通过渐近式架构,逐步整合不同层级特征,有效避免非相邻层级间因语义差距过大导致的信息丢失或降级问题,确保在多尺度特征融合过程中既能保留高层语义信息,又能融入低层细节特征。本文将AFPN应用于YOLOv11中,并配置了针对小目标的检测头,能显著增强模型对不同尺度目标的检测能力,使其在复杂场景下更聚焦于目标物体特征,减少背景等无关信息的影响。 专栏目录:YOLOv11改进目录一览 | 涉及卷积层、

Limiiiing的博客 2718

promise传/穿透和中断的理解

穿透和中断的理解 穿透是一级一级的传递下去,不是一步到达catch new Promise((resolve, reject)=>{ // resolve(2), reject(1) }).then( value=>{console.log('onResoled1()'+ value)}, reason=>{throw reason} ).then( value=>{console.log('onResoled2()'+ value)}, reason=>{thro

lxuanz的博客 545

【DevOps】rancher集成ldap,实现统一账号登录

登录到rancher控制台,选择【安全】-【认证】 选择【OpenLDAP】 其中【搜索起点】需要去ldap界面查配置完成

CN_Eden 1092

Promise源码分析(附带Promise的正确打开方式)

你还在被大佬按在地上摩擦吗?我教你Promise底层原理,让你感受一下完虐大佬的感觉。fell 倍儿爽!

漫漫前端路,进一寸有进一寸的欢喜 3818

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

promise 浏览器实现的源码_Promise源码分析(附带Promise的正确打开方式)

作者:爱编程的小和尚https://blog.youkuaiyun.com/Newbie___/article/details/107138825Promise源码分析Promise的底层是浏览器实现的,所以我只是实现了里的功能,并非和源码一摸一样。下面为部分源码,完整源码移驾到github中下载:https://github.com/young-monk/my-promise.git 我们...

weixin_39969060的博客 319

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

promise异步编排

在学习图灵学院前端代码时,看到了promise异步编排解决异步请求问题,所以总结以下文章来串通个人知识点;

m0_64231944的博客 901

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

JS 异步编程终极方案:Promise+async/await 的正确打开方式,看完豁然开朗

比如上述用户数据加载流程,用 Promise 改写后,代码会呈现出清晰的线性结构:获取 ID 的 Promise 调用.then () 处理用户信息请求,再链式调用.then () 处理权限加载,最后用一个.catch () 捕获所有环节可能出现的错误。要理解 Promise 的价值,不妨先回顾回调函数的痛点。例如,在需要并行处理多个异步操作时,可以先用 Promise.all () 将它们包装成一个 Promise,再用 await 等待结果,既保留了并行处理的效率,又维持了代码的简洁性。

2509_93051135的博客 611

Promise原理和问题集锦

参考网站: https://www.cnblogs.com/lvdabao/p/es6-promise-1.html https://www.cnblogs.com/minigrasshopper/p/9141307.html https://segmentfault.com/a/1190000011652907 promise原理 其实,promise就是三个状态。利用观察者模式...

juse__we的博客 2116

前端之Promise

在异步编程的领域中,回调地狱是一个经常困扰开发者的难题。方法就像是一场激烈的赛跑裁判,同样接收一个包含多个 Promise 实例的数组作为参数,返回一个新的 Promise 对象。该方法会返回异步任务数组中第一个执行完的结果,无论这个结果是成功还是失败,就像是宣布第一个冲过终点线的选手获胜,其他任务仍会继续执行,但它们的结果会被忽略,就像是其他选手的成绩不再被关注。Promise 是 ES6 引入的一种具有革命性的异步编程解决方案,它就像是一把神奇的钥匙,为我们打开了更高效、更优雅处理异步操作的大门。

奋斗的小前端 2795

promise对象

直接上一个例子,拉走,打开控制台,结合代码,一目了然 <html lang="en"> <head> <meta charset="utf-8"> </head> <body> <script> function sum(a, b) { const promise = new Promise...

栈木地 192

Promise详细版

/2.设置点击事件//3.创建ajax实例化对象//console.log('ajax实例化对象刚刚被创建出来:',xhr.readState)//4.打开请求//console.log('ajax已经打开了请求',xhr.readState)//5.发送请求(send方法中发送的是请求体数据,只不过get请求是没有请求体的,而post请求有请求体)xhr.send()//6.客户端接收服务端中响应回来的结果数据// 2:已经完成了请求的发送。

a_xia_o的博客 1980

已将 reject(‘出错啦‘)打开为啥还是打印的promise是fullfilled类型问题

result打印的是p.then()返回的一个新的Promise,它会立即处于fulfilled状态,而不是原始Promise的状态。原始Promise在setTimeout后 1 秒被reject,这时then的错误回调才会执行,打印'出错啦'。如果要查看Promise的真实状态或值,必须在then()或catch()中处理它,等待异步操作完成后再进行日志输出。

2301_80983231的博客 577

什么是promise

大家好,我是IT修真院郑州分院第8期学员,一枚正直善良的web程序员。 今天给大家分享一下,修真院官网JS-9任务中可能会使用到的知识点: 1.背景介绍 什么是promise? Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。 2.知识剖析   3.如何使用PROMISE?   4.解决方案 需要注意的是,new一个Promise...

jierenjia的博客 382

面试被问到的promise总结

promise.all的使用。 promise.all可以将多个promise实例包装成一个新的promise实例,并且返回的值也不相同,成功使,promise返回的值是一个结果数组,而失败的话就是返回最先响应的失败的值。 具体代码如下: let p1 = new Promise((resolve, reject) => { resolve('成功了') }) let p2 =...

一条会流泪的鱼 1117

Promise面试题之Promise.all和Promise.race的区别

转载自:https://blog.youkuaiyun.com/xyphf/article/details/77925571 一、Promise.all的使用 Promise.all可以将多个Promise实例包装成一个新的Promise实例。同事,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候返回最先被reject失败状态的值。 具体代码如下: let p1 = new ...

xyphf的博客 1984

搞定JavaScript异步原理,深入学习Promise

浏览器是多进程的,我们每打开标签页就会产生一个进程,因此打开的标签页越多,进程就越多,对CPU的消耗就越严重,从而出现卡顿。以Chrome浏览器为例,我们可以通过浏览器右侧设置-更多工具-任务管理器查看当前浏览器进程。Chrome浏览器主要进程包括1个浏览器进程(Browser进程)、1个GPU进程、1个网络进程、多个渲染进程和多个插件进程。浏览器的渲染进程是多线程的,主要由Javascript引擎线程、GUI渲染线程、事件监听线程、定时器线程、异步http请求线程等组成。Javascript引擎线程: 是

无名小小卒的博客 2372
上一篇: 使用Node.js实现一个简单的ZooKeeper客户端
下一篇: 测试用例的那一回事
IMWebteam
博客等级 码龄10年 4粉丝 0原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值